@joshski/dust 0.1.10 → 0.1.12
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 +23 -101
- package/dist/dust.js +168 -34
- package/package.json +3 -2
- package/templates/agent-help.txt +1 -1
- package/templates/agent-new-goal.txt +1 -1
- package/templates/agent-new-idea.txt +1 -1
- package/templates/agent-new-task.txt +1 -1
- package/templates/help.txt +22 -19
- package/templates/templates/agent-greeting.txt +14 -0
- package/templates/templates/agent-help.txt +35 -0
- package/templates/templates/agent-implement-task.txt +24 -0
- package/templates/templates/agent-new-goal.txt +21 -0
- package/templates/templates/agent-new-idea.txt +11 -0
- package/templates/templates/agent-new-task.txt +20 -0
- package/templates/templates/agent-pick-task.txt +7 -0
- package/templates/templates/agent-understand-goals.txt +9 -0
- package/templates/templates/agents-md.txt +5 -0
- package/templates/templates/claude-md.txt +5 -0
- package/templates/templates/help.txt +27 -0
package/README.md
CHANGED
|
@@ -1,129 +1,51 @@
|
|
|
1
1
|
# Dust
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A workflow tool for keeping AI coding agents on track.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/joshski/dust/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
## Why
|
|
7
|
+
## Why Use This?
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
AI coding agents work best with clear tasks and fast feedback. Dust gives them both:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- **Tasks** — a queue of work, each with clear requirements and definition of done
|
|
12
|
+
- **Checks** — quality gates (tests, lint, build) that run before and after changes
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Agents pick tasks, implement them, verify checks pass, and move on. You add tasks, review commits, and steer direction.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
npm install @joshski/dust
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Initialize dust in your repository:
|
|
16
|
+
## Quick Start
|
|
20
17
|
|
|
21
18
|
```bash
|
|
19
|
+
npm install @joshski/dust
|
|
22
20
|
npx dust init
|
|
23
21
|
```
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
The [.dust](./.dust) directory in your repository contains 4 sets of markdown files:
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
.dust/
|
|
31
|
-
├── goals/ # Mission statements explaining why the project exists
|
|
32
|
-
├── ideas/ # Brief notes about future tasks (intentionally vague)
|
|
33
|
-
├── tasks/ # Detailed work plans with dependencies and definition of done
|
|
34
|
-
└── facts/ # Current state: design, architecture, rules, invariants
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
These files are used to facilitate exploration and management of AI agent workflow.
|
|
38
|
-
|
|
39
|
-
## Workflow
|
|
40
|
-
|
|
41
|
-
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.
|
|
23
|
+
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.
|
|
42
24
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
Humans primarily interact with `dust` using their AI coding agent of choice, e.g.
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
> claude "implement the next task"
|
|
49
|
-
> claude "get to work!"
|
|
50
|
-
> codex "add task: coverage reporting"
|
|
51
|
-
> codex "goal: cross-platform compatibility"
|
|
52
|
-
```
|
|
25
|
+
## Running Agents
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
## Agent CLI
|
|
57
|
-
|
|
58
|
-
The `dust` CLI is intended to be used primarily by agents, not by humans (except for the `init` command, as mentioned above).
|
|
59
|
-
|
|
60
|
-
Don't let that stop you:
|
|
27
|
+
Start an agent on a single task:
|
|
61
28
|
|
|
62
29
|
```bash
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## AGENTS.md
|
|
67
|
-
|
|
68
|
-
Add a line like this to your `AGENTS.md` or `CLAUDE.md` file:
|
|
69
|
-
|
|
70
|
-
```markdown
|
|
71
|
-
Always immediately run `npx dust agent` when you start working in this repository.
|
|
30
|
+
claude "implement the next task"
|
|
72
31
|
```
|
|
73
32
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
## Configuration
|
|
33
|
+
Or let dust run agents continuously [in a sandbox](./.dust/facts/autonomous-agents-need-sandboxes.md) with the [loop](./.dust/facts/loop-command.md) command:
|
|
77
34
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```json
|
|
81
|
-
{
|
|
82
|
-
"dustCommand": "bunx dust",
|
|
83
|
-
"checks": [
|
|
84
|
-
{ "name": "lint", "command": "bunx biome check ." },
|
|
85
|
-
{ "name": "build", "command": "bun run build" },
|
|
86
|
-
{ "name": "tests", "command": "bun run test:coverage" },
|
|
87
|
-
{ "name": "typecheck", "command": "bunx tsc --noEmit lib/**/*.ts" }
|
|
88
|
-
]
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
The `dust check` command will run all of the configured checks in parallel and produce a very terse (context window-friendly) output unless one or more of the checks fail.
|
|
93
|
-
|
|
94
|
-
### Check Failure Hints
|
|
95
|
-
|
|
96
|
-
Add optional `hints` to help agents recover from check failures:
|
|
97
|
-
|
|
98
|
-
```json
|
|
99
|
-
{
|
|
100
|
-
"checks": [
|
|
101
|
-
{
|
|
102
|
-
"name": "build",
|
|
103
|
-
"command": "npm run build",
|
|
104
|
-
"hints": [
|
|
105
|
-
"Run `npm install` if this is a fresh checkout",
|
|
106
|
-
"Check for TypeScript errors in the files you modified"
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
|
-
]
|
|
110
|
-
}
|
|
35
|
+
```bash
|
|
36
|
+
npx dust loop claude
|
|
111
37
|
```
|
|
112
38
|
|
|
113
|
-
|
|
39
|
+
This runs Claude Code in a [ralph loop](https://ghuntley.com/loop/), picking up tasks until the iteration limit is reached (default: 10). You can specify a custom limit:
|
|
114
40
|
|
|
41
|
+
```bash
|
|
42
|
+
npx dust loop claude 5
|
|
115
43
|
```
|
|
116
|
-
✓ validate
|
|
117
|
-
✗ build
|
|
118
|
-
|
|
119
|
-
> npm run build
|
|
120
|
-
error TS2307: Cannot find module 'lodash'...
|
|
121
44
|
|
|
122
|
-
|
|
123
|
-
- Run `npm install` if this is a fresh checkout
|
|
124
|
-
- Check for TypeScript errors in the files you modified
|
|
45
|
+
## Learn More
|
|
125
46
|
|
|
126
|
-
|
|
127
|
-
```
|
|
47
|
+
Details live in the [.dust/facts](./.dust/facts) directory:
|
|
128
48
|
|
|
129
|
-
|
|
49
|
+
- [Directory Structure](./.dust/facts/dust-directory-structure.md) — how `.dust/` is organized
|
|
50
|
+
- [Configuration](./.dust/facts/configuration-system.md) — settings and quality checks
|
|
51
|
+
- [CLI Commands](./.dust/facts/unified-cli.md) — full command reference
|
package/dist/dust.js
CHANGED
|
@@ -254,7 +254,7 @@ async function agentUnderstandGoals(dependencies) {
|
|
|
254
254
|
// lib/cli/commands/check.ts
|
|
255
255
|
import { spawn } from "node:child_process";
|
|
256
256
|
|
|
257
|
-
// lib/cli/commands/
|
|
257
|
+
// lib/cli/commands/lint-markdown.ts
|
|
258
258
|
import { dirname as dirname2, resolve } from "node:path";
|
|
259
259
|
|
|
260
260
|
// lib/cli/markdown-utilities.ts
|
|
@@ -263,8 +263,53 @@ function extractTitle(content) {
|
|
|
263
263
|
return match ? match[1].trim() : null;
|
|
264
264
|
}
|
|
265
265
|
var MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
266
|
+
function extractOpeningSentence(content) {
|
|
267
|
+
const lines = content.split(`
|
|
268
|
+
`);
|
|
269
|
+
let h1Index = -1;
|
|
270
|
+
for (let i = 0;i < lines.length; i++) {
|
|
271
|
+
if (lines[i].match(/^#\s+.+$/)) {
|
|
272
|
+
h1Index = i;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (h1Index === -1) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
let paragraphStart = -1;
|
|
280
|
+
for (let i = h1Index + 1;i < lines.length; i++) {
|
|
281
|
+
const line = lines[i].trim();
|
|
282
|
+
if (line !== "") {
|
|
283
|
+
paragraphStart = i;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (paragraphStart === -1) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const firstLine = lines[paragraphStart];
|
|
291
|
+
const trimmedFirstLine = firstLine.trim();
|
|
292
|
+
if (trimmedFirstLine.startsWith("#") || trimmedFirstLine.startsWith("-") || trimmedFirstLine.startsWith("*") || trimmedFirstLine.startsWith("+") || trimmedFirstLine.match(/^\d+\./) || trimmedFirstLine.startsWith("```") || trimmedFirstLine.startsWith(">")) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
let paragraph = "";
|
|
296
|
+
for (let i = paragraphStart;i < lines.length; i++) {
|
|
297
|
+
const line = lines[i].trim();
|
|
298
|
+
if (line === "")
|
|
299
|
+
break;
|
|
300
|
+
if (line.startsWith("#") || line.startsWith("```") || line.startsWith(">")) {
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
paragraph += (paragraph ? " " : "") + line;
|
|
304
|
+
}
|
|
305
|
+
const sentenceMatch = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
306
|
+
if (!sentenceMatch) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
return sentenceMatch[1];
|
|
310
|
+
}
|
|
266
311
|
|
|
267
|
-
// lib/cli/commands/
|
|
312
|
+
// lib/cli/commands/lint-markdown.ts
|
|
268
313
|
var REQUIRED_HEADINGS = ["## Goals", "## Blocked by", "## Definition of done"];
|
|
269
314
|
var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
|
|
270
315
|
function validateFilename(filePath) {
|
|
@@ -278,6 +323,16 @@ function validateFilename(filePath) {
|
|
|
278
323
|
}
|
|
279
324
|
return null;
|
|
280
325
|
}
|
|
326
|
+
function validateOpeningSentence(filePath, content) {
|
|
327
|
+
const openingSentence = extractOpeningSentence(content);
|
|
328
|
+
if (!openingSentence) {
|
|
329
|
+
return {
|
|
330
|
+
file: filePath,
|
|
331
|
+
message: "Missing or malformed opening sentence after H1 heading"
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
281
336
|
function validateTaskHeadings(filePath, content) {
|
|
282
337
|
const violations = [];
|
|
283
338
|
for (const heading of REQUIRED_HEADINGS) {
|
|
@@ -380,7 +435,7 @@ function validateSemanticLinks(filePath, content) {
|
|
|
380
435
|
}
|
|
381
436
|
return violations;
|
|
382
437
|
}
|
|
383
|
-
async function
|
|
438
|
+
async function lintMarkdown(dependencies) {
|
|
384
439
|
const { context, fileSystem, globScanner: glob } = dependencies;
|
|
385
440
|
const dustPath = `${context.cwd}/.dust`;
|
|
386
441
|
if (!fileSystem.exists(dustPath)) {
|
|
@@ -397,6 +452,23 @@ async function validate(dependencies) {
|
|
|
397
452
|
const content = await fileSystem.readFile(filePath);
|
|
398
453
|
violations.push(...validateLinks(filePath, content, fileSystem));
|
|
399
454
|
}
|
|
455
|
+
const contentDirs = ["goals", "facts", "ideas", "tasks"];
|
|
456
|
+
context.stdout("Validating opening sentences...");
|
|
457
|
+
for (const dir of contentDirs) {
|
|
458
|
+
const dirPath = `${dustPath}/${dir}`;
|
|
459
|
+
if (!fileSystem.exists(dirPath))
|
|
460
|
+
continue;
|
|
461
|
+
for await (const file of glob.scan(dirPath)) {
|
|
462
|
+
if (!file.endsWith(".md"))
|
|
463
|
+
continue;
|
|
464
|
+
const filePath = `${dirPath}/${file}`;
|
|
465
|
+
const content = await fileSystem.readFile(filePath);
|
|
466
|
+
const openingSentenceViolation = validateOpeningSentence(filePath, content);
|
|
467
|
+
if (openingSentenceViolation) {
|
|
468
|
+
violations.push(openingSentenceViolation);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
400
472
|
const tasksPath = `${dustPath}/tasks`;
|
|
401
473
|
if (fileSystem.exists(tasksPath)) {
|
|
402
474
|
context.stdout("Validating task files in .dust/tasks/...");
|
|
@@ -471,14 +543,14 @@ async function runValidationCheck(dependencies) {
|
|
|
471
543
|
stdout: (msg) => outputLines.push(msg),
|
|
472
544
|
stderr: (msg) => outputLines.push(msg)
|
|
473
545
|
};
|
|
474
|
-
const result = await
|
|
546
|
+
const result = await lintMarkdown({
|
|
475
547
|
...dependencies,
|
|
476
548
|
context: bufferedContext,
|
|
477
549
|
arguments: []
|
|
478
550
|
});
|
|
479
551
|
return {
|
|
480
|
-
name: "
|
|
481
|
-
command: "dust
|
|
552
|
+
name: "lint markdown",
|
|
553
|
+
command: "dust lint markdown",
|
|
482
554
|
exitCode: result.exitCode,
|
|
483
555
|
output: outputLines.join(`
|
|
484
556
|
`),
|
|
@@ -695,6 +767,24 @@ async function init(dependencies) {
|
|
|
695
767
|
|
|
696
768
|
// lib/cli/commands/list.ts
|
|
697
769
|
var VALID_TYPES = ["tasks", "ideas", "goals", "facts"];
|
|
770
|
+
var SECTION_HEADERS = {
|
|
771
|
+
tasks: "\uD83D\uDCCB Tasks",
|
|
772
|
+
ideas: "\uD83D\uDCA1 Ideas",
|
|
773
|
+
goals: "\uD83C\uDFAF Goals",
|
|
774
|
+
facts: "\uD83D\uDCC4 Facts"
|
|
775
|
+
};
|
|
776
|
+
var TYPE_EXPLANATIONS = {
|
|
777
|
+
tasks: "Tasks are detailed work plans with dependencies and completion criteria. Each task describes a specific piece of work to be done.",
|
|
778
|
+
ideas: "Ideas are future feature notes and proposals. Ideas capture possibilities that haven't yet been refined into actionable tasks.",
|
|
779
|
+
goals: "Goals are mission statements and guiding principles. Goals describe desired outcomes and values that inform decision-making.",
|
|
780
|
+
facts: "Facts are current state documentation. Facts capture how things work today, providing context for agents and contributors."
|
|
781
|
+
};
|
|
782
|
+
var colors = {
|
|
783
|
+
reset: "\x1B[0m",
|
|
784
|
+
bold: "\x1B[1m",
|
|
785
|
+
dim: "\x1B[2m",
|
|
786
|
+
cyan: "\x1B[36m"
|
|
787
|
+
};
|
|
698
788
|
async function list(dependencies) {
|
|
699
789
|
const { arguments: commandArguments, context, fileSystem } = dependencies;
|
|
700
790
|
const dustPath = `${context.cwd}/.dust`;
|
|
@@ -709,29 +799,44 @@ async function list(dependencies) {
|
|
|
709
799
|
context.stderr(`Valid types: ${VALID_TYPES.join(", ")}`);
|
|
710
800
|
return { exitCode: 1 };
|
|
711
801
|
}
|
|
802
|
+
const specificTypeRequested = commandArguments.length > 0;
|
|
712
803
|
for (const type of typesToList) {
|
|
713
804
|
const dirPath = `${dustPath}/${type}`;
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
const files = await fileSystem.readdir(dirPath);
|
|
805
|
+
const dirExists = fileSystem.exists(dirPath);
|
|
806
|
+
const files = dirExists ? await fileSystem.readdir(dirPath) : [];
|
|
718
807
|
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
719
808
|
if (mdFiles.length === 0) {
|
|
809
|
+
if (specificTypeRequested) {
|
|
810
|
+
context.stdout(SECTION_HEADERS[type]);
|
|
811
|
+
context.stdout("");
|
|
812
|
+
context.stdout(TYPE_EXPLANATIONS[type]);
|
|
813
|
+
context.stdout("");
|
|
814
|
+
context.stdout(`No ${type} found.`);
|
|
815
|
+
context.stdout("");
|
|
816
|
+
}
|
|
720
817
|
continue;
|
|
721
818
|
}
|
|
722
|
-
context.stdout(
|
|
819
|
+
context.stdout(SECTION_HEADERS[type]);
|
|
820
|
+
context.stdout("");
|
|
821
|
+
context.stdout(TYPE_EXPLANATIONS[type]);
|
|
822
|
+
context.stdout("");
|
|
723
823
|
for (const file of mdFiles) {
|
|
724
824
|
const filePath = `${dirPath}/${file}`;
|
|
725
825
|
const content = await fileSystem.readFile(filePath);
|
|
726
826
|
const title = extractTitle(content);
|
|
727
|
-
const
|
|
827
|
+
const openingSentence = extractOpeningSentence(content);
|
|
828
|
+
const relativePath = `.dust/${type}/${file}`;
|
|
728
829
|
if (title) {
|
|
729
|
-
context.stdout(
|
|
830
|
+
context.stdout(`${colors.bold}# ${title}${colors.reset}`);
|
|
730
831
|
} else {
|
|
731
|
-
context.stdout(
|
|
832
|
+
context.stdout(`${colors.bold}# ${file.replace(".md", "")}${colors.reset}`);
|
|
833
|
+
}
|
|
834
|
+
if (openingSentence) {
|
|
835
|
+
context.stdout(`${colors.dim}${openingSentence}${colors.reset}`);
|
|
732
836
|
}
|
|
837
|
+
context.stdout(`${colors.cyan}→ ${relativePath}${colors.reset}`);
|
|
838
|
+
context.stdout("");
|
|
733
839
|
}
|
|
734
|
-
context.stdout("");
|
|
735
840
|
}
|
|
736
841
|
return { exitCode: 0 };
|
|
737
842
|
}
|
|
@@ -915,6 +1020,12 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
915
1020
|
}
|
|
916
1021
|
|
|
917
1022
|
// lib/cli/commands/next.ts
|
|
1023
|
+
var colors2 = {
|
|
1024
|
+
reset: "\x1B[0m",
|
|
1025
|
+
bold: "\x1B[1m",
|
|
1026
|
+
dim: "\x1B[2m",
|
|
1027
|
+
cyan: "\x1B[36m"
|
|
1028
|
+
};
|
|
918
1029
|
function extractBlockedBy(content) {
|
|
919
1030
|
const blockedByMatch = content.match(/^## Blocked by\s*\n([\s\S]*?)(?=\n## |\n*$)/m);
|
|
920
1031
|
if (!blockedByMatch) {
|
|
@@ -959,20 +1070,24 @@ async function next(dependencies) {
|
|
|
959
1070
|
const hasIncompleteBlocker = blockers.some((blocker) => existingTasks.has(blocker));
|
|
960
1071
|
if (!hasIncompleteBlocker) {
|
|
961
1072
|
const title = extractTitle(content);
|
|
1073
|
+
const openingSentence = extractOpeningSentence(content);
|
|
962
1074
|
const relativePath = `.dust/tasks/${file}`;
|
|
963
|
-
unblockedTasks.push({ path: relativePath, title });
|
|
1075
|
+
unblockedTasks.push({ path: relativePath, title, openingSentence });
|
|
964
1076
|
}
|
|
965
1077
|
}
|
|
966
1078
|
if (unblockedTasks.length === 0) {
|
|
967
1079
|
return { exitCode: 0 };
|
|
968
1080
|
}
|
|
969
|
-
context.stdout("Next tasks
|
|
1081
|
+
context.stdout("\uD83D\uDCCB Next tasks");
|
|
1082
|
+
context.stdout("");
|
|
970
1083
|
for (const task of unblockedTasks) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
context.stdout(
|
|
1084
|
+
const displayTitle = task.title || task.path.split("/").pop().replace(".md", "");
|
|
1085
|
+
context.stdout(`${colors2.bold}# ${displayTitle}${colors2.reset}`);
|
|
1086
|
+
if (task.openingSentence) {
|
|
1087
|
+
context.stdout(`${colors2.dim}${task.openingSentence}${colors2.reset}`);
|
|
975
1088
|
}
|
|
1089
|
+
context.stdout(`${colors2.cyan}→ ${task.path}${colors2.reset}`);
|
|
1090
|
+
context.stdout("");
|
|
976
1091
|
}
|
|
977
1092
|
return { exitCode: 0 };
|
|
978
1093
|
}
|
|
@@ -986,6 +1101,7 @@ function createDefaultDependencies() {
|
|
|
986
1101
|
};
|
|
987
1102
|
}
|
|
988
1103
|
var SLEEP_INTERVAL_MS = 30000;
|
|
1104
|
+
var DEFAULT_MAX_ITERATIONS = 10;
|
|
989
1105
|
async function gitPull(cwd, spawn2) {
|
|
990
1106
|
return new Promise((resolve2) => {
|
|
991
1107
|
const proc = spawn2("git", ["pull"], {
|
|
@@ -1022,48 +1138,66 @@ async function hasAvailableTasks(dependencies) {
|
|
|
1022
1138
|
async function runOneIteration(dependencies, loopDependencies) {
|
|
1023
1139
|
const { context } = dependencies;
|
|
1024
1140
|
const { spawn: spawn2, run: run2 } = loopDependencies;
|
|
1025
|
-
context.stdout("Syncing with remote...");
|
|
1141
|
+
context.stdout("\uD83D\uDD04 Syncing with remote...");
|
|
1026
1142
|
const pullResult = await gitPull(context.cwd, spawn2);
|
|
1027
1143
|
if (!pullResult.success) {
|
|
1028
1144
|
context.stdout(`Note: git pull skipped (${pullResult.message})`);
|
|
1029
1145
|
}
|
|
1030
|
-
context.stdout("Checking for available tasks...");
|
|
1146
|
+
context.stdout("\uD83D\uDD0D Checking for available tasks...");
|
|
1031
1147
|
const hasTasks = await hasAvailableTasks(dependencies);
|
|
1032
1148
|
if (!hasTasks) {
|
|
1033
|
-
context.stdout("No tasks available. Sleeping...");
|
|
1149
|
+
context.stdout("\uD83D\uDCA4 No tasks available. Sleeping...");
|
|
1034
1150
|
context.stdout("");
|
|
1035
1151
|
return "no_tasks";
|
|
1036
1152
|
}
|
|
1037
|
-
context.stdout("Found task(s). Starting Claude...");
|
|
1153
|
+
context.stdout("✨ Found task(s). \uD83E\uDD16 Starting Claude...");
|
|
1038
1154
|
context.stdout("");
|
|
1039
1155
|
try {
|
|
1040
1156
|
await run2("go", { cwd: context.cwd, dangerouslySkipPermissions: true });
|
|
1041
1157
|
context.stdout("");
|
|
1042
|
-
context.stdout("Claude session complete. Continuing loop...");
|
|
1158
|
+
context.stdout("✅ Claude session complete. Continuing loop...");
|
|
1043
1159
|
context.stdout("");
|
|
1044
1160
|
return "ran_claude";
|
|
1045
1161
|
} catch (error) {
|
|
1046
1162
|
const message = error instanceof Error ? error.message : String(error);
|
|
1047
1163
|
context.stderr(`Claude exited with error: ${message}`);
|
|
1048
1164
|
context.stdout("");
|
|
1049
|
-
context.stdout("Claude session complete. Continuing loop...");
|
|
1165
|
+
context.stdout("✅ Claude session complete. Continuing loop...");
|
|
1050
1166
|
context.stdout("");
|
|
1051
1167
|
return "claude_error";
|
|
1052
1168
|
}
|
|
1053
1169
|
}
|
|
1054
|
-
|
|
1170
|
+
function parseMaxIterations(commandArguments) {
|
|
1171
|
+
if (commandArguments.length === 0) {
|
|
1172
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
1173
|
+
}
|
|
1174
|
+
const parsed = Number.parseInt(commandArguments[0], 10);
|
|
1175
|
+
if (Number.isNaN(parsed) || parsed <= 0) {
|
|
1176
|
+
return DEFAULT_MAX_ITERATIONS;
|
|
1177
|
+
}
|
|
1178
|
+
return parsed;
|
|
1179
|
+
}
|
|
1180
|
+
async function loopClaude(dependencies, loopDependencies = createDefaultDependencies()) {
|
|
1055
1181
|
const { context } = dependencies;
|
|
1056
|
-
|
|
1182
|
+
const maxIterations = parseMaxIterations(dependencies.arguments);
|
|
1183
|
+
context.stdout("⚠️ WARNING: This command skips all permission checks. Only use in a sandbox environment!");
|
|
1057
1184
|
context.stdout("");
|
|
1058
|
-
context.stdout(
|
|
1059
|
-
context.stdout("Press Ctrl+C to stop");
|
|
1185
|
+
context.stdout(`\uD83D\uDD04 Starting dust loop claude (max ${maxIterations} iterations)...`);
|
|
1186
|
+
context.stdout(" Press Ctrl+C to stop");
|
|
1060
1187
|
context.stdout("");
|
|
1061
|
-
|
|
1188
|
+
let completedIterations = 0;
|
|
1189
|
+
while (completedIterations < maxIterations) {
|
|
1062
1190
|
const result = await runOneIteration(dependencies, loopDependencies);
|
|
1063
1191
|
if (result === "no_tasks") {
|
|
1064
1192
|
await loopDependencies.sleep(SLEEP_INTERVAL_MS);
|
|
1193
|
+
} else {
|
|
1194
|
+
completedIterations++;
|
|
1195
|
+
context.stdout(`\uD83D\uDCCB Completed iteration ${completedIterations}/${maxIterations}`);
|
|
1196
|
+
context.stdout("");
|
|
1065
1197
|
}
|
|
1066
1198
|
}
|
|
1199
|
+
context.stdout(`\uD83C\uDFC1 Reached max iterations (${maxIterations}). Exiting.`);
|
|
1200
|
+
return { exitCode: 0 };
|
|
1067
1201
|
}
|
|
1068
1202
|
|
|
1069
1203
|
// lib/cli/commands/pre-push.ts
|
|
@@ -1074,7 +1208,7 @@ async function prePush(dependencies) {
|
|
|
1074
1208
|
// lib/cli/main.ts
|
|
1075
1209
|
var commandRegistry = {
|
|
1076
1210
|
init,
|
|
1077
|
-
|
|
1211
|
+
"lint-markdown": lintMarkdown,
|
|
1078
1212
|
list,
|
|
1079
1213
|
next,
|
|
1080
1214
|
check,
|
|
@@ -1086,7 +1220,7 @@ var commandRegistry = {
|
|
|
1086
1220
|
"agent-implement-task": agentImplementTask,
|
|
1087
1221
|
"agent-pick-task": agentPickTask,
|
|
1088
1222
|
"agent-understand-goals": agentUnderstandGoals,
|
|
1089
|
-
loop,
|
|
1223
|
+
"loop-claude": loopClaude,
|
|
1090
1224
|
"pre-push": prePush,
|
|
1091
1225
|
help
|
|
1092
1226
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshski/dust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "A lightweight planning system for human-AI collaboration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "bun build lib/cli/entry.ts --target node --outfile dist/dust.js && printf '%s\\n%s' '#!/usr/bin/env node' \"$(cat dist/dust.js)\" > dist/dust.js && cp -r lib/templates templates",
|
|
28
28
|
"test": "vitest run",
|
|
29
|
-
"test:coverage": "vitest run --coverage"
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"eval": "bun run ./evals/run.ts"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
33
|
"@biomejs/biome": "^2.3.13",
|
package/templates/agent-help.txt
CHANGED
|
@@ -11,7 +11,7 @@ Dust is a lightweight planning system. The `.dust/` directory contains:
|
|
|
11
11
|
- `{{bin}} check` - Run quality gates (do this before and after work)
|
|
12
12
|
- `{{bin}} next` - Show tasks ready to work on
|
|
13
13
|
- `{{bin}} list [type]` - List artifacts (tasks, ideas, goals, facts)
|
|
14
|
-
- `{{bin}}
|
|
14
|
+
- `{{bin}} lint markdown` - Check .dust/ files for errors
|
|
15
15
|
|
|
16
16
|
**Workflow:** Pick a task, implement it, delete the task file, commit atomically.
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@ Follow these steps:
|
|
|
11
11
|
- What this goal means in practice
|
|
12
12
|
- Why it matters for the project
|
|
13
13
|
- How to evaluate whether work supports this goal
|
|
14
|
-
5. Run `{{bin}}
|
|
14
|
+
5. Run `{{bin}} lint markdown` to catch any formatting issues
|
|
15
15
|
6. Create a single atomic commit with a message in the format "Add goal: <title>"
|
|
16
16
|
7. Push your commit to the remote repository
|
|
17
17
|
|
|
@@ -6,6 +6,6 @@ Follow these steps:
|
|
|
6
6
|
2. Create a new markdown file in `.dust/ideas/` with a descriptive kebab-case name (e.g., `improve-error-messages.md`)
|
|
7
7
|
3. Add a title as the first line using an H1 heading (e.g., `# Improve error messages`)
|
|
8
8
|
4. Write a brief description of the potential change or improvement
|
|
9
|
-
5. Run `{{bin}}
|
|
9
|
+
5. Run `{{bin}} lint markdown` to catch any issues with the idea file format
|
|
10
10
|
6. Create a single atomic commit with a message in the format "Add idea: <title>"
|
|
11
11
|
7. Push your commit to the remote repository
|
|
@@ -12,7 +12,7 @@ Follow these steps:
|
|
|
12
12
|
6. Add a `## Goals` section with links to relevant goals this task supports (e.g., `- [Goal Name](../goals/goal-name.md)`)
|
|
13
13
|
7. Add a `## Blocked by` section listing any tasks that must complete first, or `(none)` if there are no blockers
|
|
14
14
|
8. Add a `## Definition of done` section with a checklist of completion criteria using `- [ ]` for each item
|
|
15
|
-
9. Run `{{bin}}
|
|
15
|
+
9. Run `{{bin}} lint markdown` to catch any issues with the task format
|
|
16
16
|
10. Create a single atomic commit with a message in the format "Add task: <title>" that includes:
|
|
17
17
|
- The new task file
|
|
18
18
|
- Deletion of any ideas that were fully realized
|
package/templates/help.txt
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
dust - A
|
|
1
|
+
💨 dust - A workflow tool for keeping AI coding agents on track.
|
|
2
2
|
|
|
3
3
|
Usage: {{bin}} <command> [options]
|
|
4
4
|
|
|
5
5
|
Commands:
|
|
6
|
-
init
|
|
7
|
-
|
|
8
|
-
list
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
init Initialize a new Dust repository
|
|
7
|
+
lint markdown Run lint checks on .dust/ files
|
|
8
|
+
list List all items (tasks, ideas, goals, facts)
|
|
9
|
+
list tasks List tasks (actionable work with definitions of done)
|
|
10
|
+
list ideas List ideas (vague proposals, convert to tasks when ready)
|
|
11
|
+
list goals List goals (guiding principles, stable, rarely change)
|
|
12
|
+
list facts List facts (documentation of current system state)
|
|
13
|
+
next Show tasks ready to work on (not blocked)
|
|
14
|
+
check Run project-defined quality gate hook
|
|
15
|
+
agent Agent greeting and routing instructions
|
|
16
|
+
agent help Show agent guidance
|
|
17
|
+
agent pick task Pick the next task to work on
|
|
18
|
+
agent implement task Implement a task
|
|
19
|
+
agent new task Create a new task
|
|
20
|
+
agent new goal Create a new goal
|
|
21
|
+
agent new idea Working with ideas
|
|
22
|
+
agent understand goals Understanding goals
|
|
23
|
+
loop Run continuous Claude iteration on tasks
|
|
24
|
+
pre-push Git pre-push hook validation
|
|
25
|
+
help Show this help message
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
{{bin}} init
|
|
17
|
-
{{bin}} validate
|
|
18
|
-
{{bin}} list tasks
|
|
19
|
-
{{bin}} list
|
|
20
|
-
{{bin}} next
|
|
21
|
-
{{bin}} check
|
|
22
|
-
{{bin}} agent work
|
|
23
|
-
|
|
24
|
-
If you are an AI agent, run `{{bin}} agent` now!
|
|
27
|
+
🤖 If you are an AI agent, run `{{bin}} agent` to get started!
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Hello Agent, welcome to dust!
|
|
2
|
+
|
|
3
|
+
CRITICAL: You MUST run exactly ONE of the commands below before doing anything else.
|
|
4
|
+
|
|
5
|
+
Based on what the user asked, run the matching command NOW:
|
|
6
|
+
|
|
7
|
+
1. "work", "go", "pick a task", "implement a task" → `{{bin}} agent pick task`
|
|
8
|
+
2. "implement [task name]" → `{{bin}} agent implement task`
|
|
9
|
+
3. "task: ..." or "add a task ..." → `{{bin}} agent new task`
|
|
10
|
+
4. "goal: ..." or "add a goal ..." → `{{bin}} agent new goal`
|
|
11
|
+
5. A vague idea about a potential change → `{{bin}} agent new idea`
|
|
12
|
+
6. Anything else → `{{bin}} agent help`
|
|
13
|
+
|
|
14
|
+
Do NOT proceed without running one of these commands.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Dust Agent Guide
|
|
2
|
+
|
|
3
|
+
Dust is a lightweight planning system. The `.dust/` directory contains:
|
|
4
|
+
|
|
5
|
+
- **goals/** - Guiding principles (stable, rarely change)
|
|
6
|
+
- **ideas/** - Vague proposals (convert to tasks when ready)
|
|
7
|
+
- **tasks/** - Actionable work with definitions of done
|
|
8
|
+
- **facts/** - Documentation of current system state
|
|
9
|
+
|
|
10
|
+
**Key commands:**
|
|
11
|
+
- `{{bin}} check` - Run quality gates (do this before and after work)
|
|
12
|
+
- `{{bin}} next` - Show tasks ready to work on
|
|
13
|
+
- `{{bin}} list [type]` - List artifacts (tasks, ideas, goals, facts)
|
|
14
|
+
- `{{bin}} lint markdown` - Check .dust/ files for errors
|
|
15
|
+
|
|
16
|
+
**Workflow:** Pick a task, implement it, delete the task file, commit atomically.
|
|
17
|
+
|
|
18
|
+
For focused guidance, run:
|
|
19
|
+
- `{{bin}} agent pick task` - Pick the next task to work on
|
|
20
|
+
- `{{bin}} agent implement task` - Implement a task
|
|
21
|
+
- `{{bin}} agent new task` - Create a new task
|
|
22
|
+
- `{{bin}} agent new goal` - Create a new goal
|
|
23
|
+
- `{{bin}} agent new idea` - Working with ideas
|
|
24
|
+
- `{{bin}} agent understand goals` - Understanding goals
|
|
25
|
+
|
|
26
|
+
### Configuring Agent Files
|
|
27
|
+
|
|
28
|
+
Projects using dust should add a minimal pointer to their agent configuration files (CLAUDE.md, AGENTS.md, etc.):
|
|
29
|
+
|
|
30
|
+
```markdown
|
|
31
|
+
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
|
|
32
|
+
Always run `dust agent` when you start working in this repository.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This keeps agent instructions minimal and ensures agents get current documentation.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## Implement a Task
|
|
2
|
+
|
|
3
|
+
Follow these steps:
|
|
4
|
+
|
|
5
|
+
1. {{installDependenciesHint}} (unless already installed)
|
|
6
|
+
2. Run `{{bin}} check` to verify the project is in a good state
|
|
7
|
+
3. Implement the task
|
|
8
|
+
{{#unless hooksInstalled}}4. Run `{{bin}} check` before committing
|
|
9
|
+
5.{{/unless}}{{#if hooksInstalled}}4.{{/if}} Create a single atomic commit that includes:
|
|
10
|
+
- All implementation changes
|
|
11
|
+
- Deletion of the completed task file
|
|
12
|
+
- Updates to any facts that changed
|
|
13
|
+
- Deletion of any ideas that were fully realized
|
|
14
|
+
|
|
15
|
+
Use the task title as the commit message. Task titles are written in imperative form, which is the recommended style for git commit messages. Do not add prefixes like "Complete task:" - use the title directly.
|
|
16
|
+
|
|
17
|
+
Example: If the task title is "Add validation for user input", the commit message should be:
|
|
18
|
+
```
|
|
19
|
+
Add validation for user input
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
{{#unless hooksInstalled}}6.{{/unless}}{{#if hooksInstalled}}5.{{/if}} Push your commit to the remote repository
|
|
23
|
+
|
|
24
|
+
Keep your change small and focused. One task, one commit.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Adding a New Goal
|
|
2
|
+
|
|
3
|
+
Goals are guiding principles that persist across tasks. They define the "why" behind the work.
|
|
4
|
+
|
|
5
|
+
Follow these steps:
|
|
6
|
+
|
|
7
|
+
1. Run `{{bin}} list goals` to see existing goals and avoid duplication
|
|
8
|
+
2. Create a new markdown file in `.dust/goals/` with a descriptive kebab-case name (e.g., `cross-platform-support.md`)
|
|
9
|
+
3. Add a title as the first line using an H1 heading (e.g., `# Cross-platform support`)
|
|
10
|
+
4. Write a clear description explaining:
|
|
11
|
+
- What this goal means in practice
|
|
12
|
+
- Why it matters for the project
|
|
13
|
+
- How to evaluate whether work supports this goal
|
|
14
|
+
5. Run `{{bin}} lint markdown` to catch any formatting issues
|
|
15
|
+
6. Create a single atomic commit with a message in the format "Add goal: <title>"
|
|
16
|
+
7. Push your commit to the remote repository
|
|
17
|
+
|
|
18
|
+
Goals should be:
|
|
19
|
+
- **Stable** - They rarely change once established
|
|
20
|
+
- **Actionable** - Tasks can be linked to them
|
|
21
|
+
- **Clear** - Anyone reading should understand what it means
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## Adding a New Idea
|
|
2
|
+
|
|
3
|
+
Follow these steps:
|
|
4
|
+
|
|
5
|
+
1. Run `{{bin}} list ideas` to see all existing ideas and avoid duplicates
|
|
6
|
+
2. Create a new markdown file in `.dust/ideas/` with a descriptive kebab-case name (e.g., `improve-error-messages.md`)
|
|
7
|
+
3. Add a title as the first line using an H1 heading (e.g., `# Improve error messages`)
|
|
8
|
+
4. Write a brief description of the potential change or improvement
|
|
9
|
+
5. Run `{{bin}} lint markdown` to catch any issues with the idea file format
|
|
10
|
+
6. Create a single atomic commit with a message in the format "Add idea: <title>"
|
|
11
|
+
7. Push your commit to the remote repository
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Adding a New Task
|
|
2
|
+
|
|
3
|
+
Follow these steps:
|
|
4
|
+
|
|
5
|
+
1. Run `{{bin}} list ideas` to see all existing ideas
|
|
6
|
+
2. Determine which ideas (if any) should be:
|
|
7
|
+
- **Deleted** - if the new task fully covers the idea
|
|
8
|
+
- **Updated** - if the idea's scope changes as a result of the task
|
|
9
|
+
3. Create a new markdown file in `.dust/tasks/` with a descriptive kebab-case name (e.g., `add-user-authentication.md`)
|
|
10
|
+
4. Add a title as the first line using an H1 heading (e.g., `# Add user authentication`)
|
|
11
|
+
5. Write a comprehensive description of what needs to be done with technical details and references to relevant files
|
|
12
|
+
6. Add a `## Goals` section with links to relevant goals this task supports (e.g., `- [Goal Name](../goals/goal-name.md)`)
|
|
13
|
+
7. Add a `## Blocked by` section listing any tasks that must complete first, or `(none)` if there are no blockers
|
|
14
|
+
8. Add a `## Definition of done` section with a checklist of completion criteria using `- [ ]` for each item
|
|
15
|
+
9. Run `{{bin}} lint markdown` to catch any issues with the task format
|
|
16
|
+
10. Create a single atomic commit with a message in the format "Add task: <title>" that includes:
|
|
17
|
+
- The new task file
|
|
18
|
+
- Deletion of any ideas that were fully realized
|
|
19
|
+
- Updates to any ideas whose scope changed
|
|
20
|
+
11. Push your commit to the remote repository
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## Understanding Goals
|
|
2
|
+
|
|
3
|
+
**List goals:** `{{bin}} list goals`
|
|
4
|
+
|
|
5
|
+
Goals live in `.dust/goals/` as markdown files. They define the project's guiding principles and priorities.
|
|
6
|
+
|
|
7
|
+
Goals are linked from tasks to show which principles each task supports. When working on a task, you can read its linked goals for context on why the work matters.
|
|
8
|
+
|
|
9
|
+
Goals are stable—they rarely change. Tasks come and go, but goals persist.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
💨 dust - A workflow tool for keeping AI coding agents on track.
|
|
2
|
+
|
|
3
|
+
Usage: {{bin}} <command> [options]
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
init Initialize a new Dust repository
|
|
7
|
+
lint markdown Run lint checks on .dust/ files
|
|
8
|
+
list List all items (tasks, ideas, goals, facts)
|
|
9
|
+
list tasks List tasks (actionable work with definitions of done)
|
|
10
|
+
list ideas List ideas (vague proposals, convert to tasks when ready)
|
|
11
|
+
list goals List goals (guiding principles, stable, rarely change)
|
|
12
|
+
list facts List facts (documentation of current system state)
|
|
13
|
+
next Show tasks ready to work on (not blocked)
|
|
14
|
+
check Run project-defined quality gate hook
|
|
15
|
+
agent Agent greeting and routing instructions
|
|
16
|
+
agent help Show agent guidance
|
|
17
|
+
agent pick task Pick the next task to work on
|
|
18
|
+
agent implement task Implement a task
|
|
19
|
+
agent new task Create a new task
|
|
20
|
+
agent new goal Create a new goal
|
|
21
|
+
agent new idea Working with ideas
|
|
22
|
+
agent understand goals Understanding goals
|
|
23
|
+
loop Run continuous Claude iteration on tasks
|
|
24
|
+
pre-push Git pre-push hook validation
|
|
25
|
+
help Show this help message
|
|
26
|
+
|
|
27
|
+
🤖 If you are an AI agent, run `{{bin}} agent` to get started!
|