@joshski/dust 0.1.7 → 0.1.8
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 +58 -5
- package/dist/dust.js +144 -114
- package/package.json +1 -1
- package/templates/agent-greeting.txt +1 -1
package/README.md
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
# Dust
|
|
2
2
|
|
|
3
|
-
A lightweight
|
|
3
|
+
A lightweight workflow tool for humans working with AI agents.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/joshski/dust/actions/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
+
## Why Would I Use This?
|
|
8
|
+
|
|
9
|
+
Use this to plan a series of tasks that coding agents can perform autonomously.
|
|
10
|
+
|
|
7
11
|
## Getting Started
|
|
8
12
|
|
|
9
|
-
Install dust:
|
|
13
|
+
Install dust using your package manager of choice (`npm` and `bun` officially supported for now):
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
npm install
|
|
16
|
+
npm install @joshski/dust
|
|
13
17
|
```
|
|
14
18
|
|
|
15
19
|
Initialize dust in your repository:
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
22
|
npx dust init
|
|
19
|
-
# or
|
|
20
|
-
bunx dust init
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
## How It Works
|
|
@@ -39,3 +41,54 @@ These files are used to facilitate exploration and management of AI agent workfl
|
|
|
39
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.
|
|
40
42
|
|
|
41
43
|
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.
|
|
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
|
+
```
|
|
53
|
+
|
|
54
|
+
Agents discover everything they need to know to interpret human prompts using the `dust` CLI.
|
|
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:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx dust help
|
|
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.
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
In fact, you might find it effective to use that as the _only_ line in those files. Essential documentation should be discoverable by agents exploring your `./.dust` directory.
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
Configure hooks into your tools in `./.dust/config/settings.json` e.g.
|
|
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
|
+
Agents are instructed to run `dust check` before and after any changes, as a way of keeping them on track. It's more important that these commands are comprehensive, than they are fast.
|
package/dist/dust.js
CHANGED
|
@@ -18,7 +18,7 @@ function loadTemplate(name, variables = {}) {
|
|
|
18
18
|
return content;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// lib/cli/agent.ts
|
|
21
|
+
// lib/cli/commands/agent.ts
|
|
22
22
|
var AGENT_SUBCOMMANDS = [
|
|
23
23
|
"work",
|
|
24
24
|
"tasks",
|
|
@@ -26,45 +26,32 @@ var AGENT_SUBCOMMANDS = [
|
|
|
26
26
|
"ideas",
|
|
27
27
|
"help"
|
|
28
28
|
];
|
|
29
|
-
function
|
|
30
|
-
return
|
|
29
|
+
function templateVariables(settings) {
|
|
30
|
+
return { bin: settings.dustCommand };
|
|
31
31
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
function generateTasksInstructions(settings) {
|
|
36
|
-
return loadTemplate("agent-tasks", { bin: settings.dustCommand });
|
|
37
|
-
}
|
|
38
|
-
function generateGoalsInstructions(settings) {
|
|
39
|
-
return loadTemplate("agent-goals", { bin: settings.dustCommand });
|
|
40
|
-
}
|
|
41
|
-
function generateIdeasInstructions(settings) {
|
|
42
|
-
return loadTemplate("agent-ideas", { bin: settings.dustCommand });
|
|
43
|
-
}
|
|
44
|
-
function generateAgentHelp(settings) {
|
|
45
|
-
return loadTemplate("agent-help", { bin: settings.dustCommand });
|
|
46
|
-
}
|
|
47
|
-
async function agent(ctx, args, settings) {
|
|
32
|
+
async function agent(deps) {
|
|
33
|
+
const { arguments: args, context: ctx, settings } = deps;
|
|
48
34
|
const subcommand = args[0];
|
|
35
|
+
const vars = templateVariables(settings);
|
|
49
36
|
if (!subcommand) {
|
|
50
|
-
ctx.stdout(
|
|
37
|
+
ctx.stdout(loadTemplate("agent-greeting", vars));
|
|
51
38
|
return { exitCode: 0 };
|
|
52
39
|
}
|
|
53
40
|
switch (subcommand) {
|
|
54
41
|
case "work":
|
|
55
|
-
ctx.stdout(
|
|
42
|
+
ctx.stdout(loadTemplate("agent-work", vars));
|
|
56
43
|
return { exitCode: 0 };
|
|
57
44
|
case "tasks":
|
|
58
|
-
ctx.stdout(
|
|
45
|
+
ctx.stdout(loadTemplate("agent-tasks", vars));
|
|
59
46
|
return { exitCode: 0 };
|
|
60
47
|
case "goals":
|
|
61
|
-
ctx.stdout(
|
|
48
|
+
ctx.stdout(loadTemplate("agent-goals", vars));
|
|
62
49
|
return { exitCode: 0 };
|
|
63
50
|
case "ideas":
|
|
64
|
-
ctx.stdout(
|
|
51
|
+
ctx.stdout(loadTemplate("agent-ideas", vars));
|
|
65
52
|
return { exitCode: 0 };
|
|
66
53
|
case "help":
|
|
67
|
-
ctx.stdout(
|
|
54
|
+
ctx.stdout(loadTemplate("agent-help", vars));
|
|
68
55
|
return { exitCode: 0 };
|
|
69
56
|
default:
|
|
70
57
|
ctx.stderr(`Unknown subcommand: ${subcommand}`);
|
|
@@ -73,59 +60,20 @@ async function agent(ctx, args, settings) {
|
|
|
73
60
|
}
|
|
74
61
|
}
|
|
75
62
|
|
|
76
|
-
// lib/cli/check.ts
|
|
63
|
+
// lib/cli/commands/check.ts
|
|
77
64
|
import { spawn } from "node:child_process";
|
|
78
65
|
|
|
79
|
-
// lib/cli/
|
|
80
|
-
import {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
}
|
|
66
|
+
// lib/cli/commands/validate.ts
|
|
67
|
+
import { dirname as dirname2, resolve } from "node:path";
|
|
68
|
+
|
|
69
|
+
// lib/cli/markdown-utilities.ts
|
|
70
|
+
function extractTitle(content) {
|
|
71
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
72
|
+
return match ? match[1].trim() : null;
|
|
125
73
|
}
|
|
74
|
+
var MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
126
75
|
|
|
127
|
-
// lib/cli/validate.ts
|
|
128
|
-
import { dirname as dirname2, resolve } from "node:path";
|
|
76
|
+
// lib/cli/commands/validate.ts
|
|
129
77
|
var REQUIRED_HEADINGS = ["## Goals", "## Blocked by", "## Definition of done"];
|
|
130
78
|
var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
|
|
131
79
|
function validateFilename(filePath) {
|
|
@@ -158,7 +106,7 @@ function validateLinks(filePath, content, fs) {
|
|
|
158
106
|
const fileDir = dirname2(filePath);
|
|
159
107
|
for (let i = 0;i < lines.length; i++) {
|
|
160
108
|
const line = lines[i];
|
|
161
|
-
const linkPattern =
|
|
109
|
+
const linkPattern = new RegExp(MARKDOWN_LINK_PATTERN.source, "g");
|
|
162
110
|
let match = linkPattern.exec(line);
|
|
163
111
|
while (match) {
|
|
164
112
|
const linkTarget = match[2];
|
|
@@ -205,7 +153,7 @@ function validateSemanticLinks(filePath, content) {
|
|
|
205
153
|
const rule = SEMANTIC_RULES.find((r) => r.section === currentSection);
|
|
206
154
|
if (!rule)
|
|
207
155
|
continue;
|
|
208
|
-
const linkPattern =
|
|
156
|
+
const linkPattern = new RegExp(MARKDOWN_LINK_PATTERN.source, "g");
|
|
209
157
|
let match = linkPattern.exec(line);
|
|
210
158
|
while (match) {
|
|
211
159
|
const linkTarget = match[2];
|
|
@@ -241,7 +189,8 @@ function validateSemanticLinks(filePath, content) {
|
|
|
241
189
|
}
|
|
242
190
|
return violations;
|
|
243
191
|
}
|
|
244
|
-
async function validate(
|
|
192
|
+
async function validate(deps) {
|
|
193
|
+
const { context: ctx, fileSystem: fs, globScanner: glob } = deps;
|
|
245
194
|
const dustPath = `${ctx.cwd}/.dust`;
|
|
246
195
|
if (!fs.exists(dustPath)) {
|
|
247
196
|
ctx.stderr("Error: .dust directory not found");
|
|
@@ -287,7 +236,7 @@ async function validate(ctx, fs, _args, glob) {
|
|
|
287
236
|
return { exitCode: 1 };
|
|
288
237
|
}
|
|
289
238
|
|
|
290
|
-
// lib/cli/check.ts
|
|
239
|
+
// lib/cli/commands/check.ts
|
|
291
240
|
function createBufferedRunner(spawnFn) {
|
|
292
241
|
return {
|
|
293
242
|
run: (command, cwd) => {
|
|
@@ -323,14 +272,18 @@ async function runConfiguredChecks(checks, cwd, runner) {
|
|
|
323
272
|
});
|
|
324
273
|
return Promise.all(promises);
|
|
325
274
|
}
|
|
326
|
-
async function runValidationCheck(
|
|
275
|
+
async function runValidationCheck(deps) {
|
|
327
276
|
const outputLines = [];
|
|
328
277
|
const bufferedCtx = {
|
|
329
|
-
cwd:
|
|
278
|
+
cwd: deps.context.cwd,
|
|
330
279
|
stdout: (msg) => outputLines.push(msg),
|
|
331
280
|
stderr: (msg) => outputLines.push(msg)
|
|
332
281
|
};
|
|
333
|
-
const result = await validate(
|
|
282
|
+
const result = await validate({
|
|
283
|
+
...deps,
|
|
284
|
+
context: bufferedCtx,
|
|
285
|
+
arguments: []
|
|
286
|
+
});
|
|
334
287
|
return {
|
|
335
288
|
name: "validate",
|
|
336
289
|
command: "dust validate",
|
|
@@ -361,8 +314,8 @@ function displayResults(results, ctx) {
|
|
|
361
314
|
ctx.stdout(`${passed.length}/${results.length} checks passed`);
|
|
362
315
|
return failed.length > 0 ? 1 : 0;
|
|
363
316
|
}
|
|
364
|
-
async function check(
|
|
365
|
-
const
|
|
317
|
+
async function check(deps, bufferedRunner = defaultBufferedRunner) {
|
|
318
|
+
const { context: ctx, fileSystem: fs, settings } = deps;
|
|
366
319
|
if (!settings.checks || settings.checks.length === 0) {
|
|
367
320
|
ctx.stderr("Error: No checks configured in .dust/config/settings.json");
|
|
368
321
|
ctx.stderr("");
|
|
@@ -376,8 +329,9 @@ async function check(ctx, fs, _args, glob, bufferedRunner = defaultBufferedRunne
|
|
|
376
329
|
return { exitCode: 1 };
|
|
377
330
|
}
|
|
378
331
|
const checkPromises = [];
|
|
379
|
-
|
|
380
|
-
|
|
332
|
+
const dustPath = `${ctx.cwd}/.dust`;
|
|
333
|
+
if (fs.exists(dustPath)) {
|
|
334
|
+
checkPromises.push(runValidationCheck(deps));
|
|
381
335
|
}
|
|
382
336
|
checkPromises.push(runConfiguredChecks(settings.checks, ctx.cwd, bufferedRunner));
|
|
383
337
|
const promiseResults = await Promise.all(checkPromises);
|
|
@@ -393,13 +347,74 @@ async function check(ctx, fs, _args, glob, bufferedRunner = defaultBufferedRunne
|
|
|
393
347
|
return { exitCode };
|
|
394
348
|
}
|
|
395
349
|
|
|
396
|
-
// lib/cli/
|
|
397
|
-
|
|
350
|
+
// lib/cli/settings.ts
|
|
351
|
+
import { join as join2 } from "node:path";
|
|
352
|
+
var DEFAULT_SETTINGS = {
|
|
353
|
+
dustCommand: "npx dust"
|
|
354
|
+
};
|
|
355
|
+
function detectDustCommand(cwd, fs) {
|
|
356
|
+
if (fs.exists(join2(cwd, "bun.lockb"))) {
|
|
357
|
+
return "bunx dust";
|
|
358
|
+
}
|
|
359
|
+
if (fs.exists(join2(cwd, "pnpm-lock.yaml"))) {
|
|
360
|
+
return "pnpx dust";
|
|
361
|
+
}
|
|
362
|
+
if (fs.exists(join2(cwd, "package-lock.json"))) {
|
|
363
|
+
return "npx dust";
|
|
364
|
+
}
|
|
365
|
+
if (process.env.BUN_INSTALL) {
|
|
366
|
+
return "bunx dust";
|
|
367
|
+
}
|
|
368
|
+
return "npx dust";
|
|
369
|
+
}
|
|
370
|
+
async function loadSettings(cwd, fs) {
|
|
371
|
+
const settingsPath = join2(cwd, ".dust", "config", "settings.json");
|
|
372
|
+
if (!fs.exists(settingsPath)) {
|
|
373
|
+
return {
|
|
374
|
+
dustCommand: detectDustCommand(cwd, fs)
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const content = await fs.readFile(settingsPath);
|
|
379
|
+
const parsed = JSON.parse(content);
|
|
380
|
+
if (!parsed.dustCommand) {
|
|
381
|
+
return {
|
|
382
|
+
...DEFAULT_SETTINGS,
|
|
383
|
+
...parsed,
|
|
384
|
+
dustCommand: detectDustCommand(cwd, fs)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
...DEFAULT_SETTINGS,
|
|
389
|
+
...parsed
|
|
390
|
+
};
|
|
391
|
+
} catch {
|
|
392
|
+
return {
|
|
393
|
+
dustCommand: detectDustCommand(cwd, fs)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// lib/cli/commands/init.ts
|
|
399
|
+
var DUST_DIRECTORIES = ["goals", "ideas", "tasks", "facts", "config"];
|
|
400
|
+
function generateSettings(cwd, fs) {
|
|
401
|
+
const dustCommand = detectDustCommand(cwd, fs);
|
|
402
|
+
const checks = [];
|
|
403
|
+
if (fs.exists(`${cwd}/bun.lockb`)) {
|
|
404
|
+
checks.push({ name: "test", command: "bun test" });
|
|
405
|
+
} else if (fs.exists(`${cwd}/pnpm-lock.yaml`)) {
|
|
406
|
+
checks.push({ name: "test", command: "pnpm test" });
|
|
407
|
+
} else if (fs.exists(`${cwd}/package-lock.json`) || fs.exists(`${cwd}/package.json`)) {
|
|
408
|
+
checks.push({ name: "test", command: "npm test" });
|
|
409
|
+
}
|
|
410
|
+
return { dustCommand, checks };
|
|
411
|
+
}
|
|
398
412
|
var USE_DUST_FACT = `# Use dust for planning
|
|
399
413
|
|
|
400
414
|
This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
|
|
401
415
|
`;
|
|
402
|
-
async function init(
|
|
416
|
+
async function init(deps) {
|
|
417
|
+
const { context: ctx, fileSystem: fs } = deps;
|
|
403
418
|
const dustPath = `${ctx.cwd}/.dust`;
|
|
404
419
|
const dustCommand = detectDustCommand(ctx.cwd, fs);
|
|
405
420
|
const agentInstruction = `Always immediately run \`${dustCommand} agent\` when you start working in this repository.`;
|
|
@@ -411,9 +426,13 @@ async function init(ctx, fs, _args) {
|
|
|
411
426
|
await fs.mkdir(`${dustPath}/${dir}`, { recursive: true });
|
|
412
427
|
}
|
|
413
428
|
await fs.writeFile(`${dustPath}/facts/use-dust-for-planning.md`, USE_DUST_FACT);
|
|
429
|
+
const settings = generateSettings(ctx.cwd, fs);
|
|
430
|
+
await fs.writeFile(`${dustPath}/config/settings.json`, `${JSON.stringify(settings, null, 2)}
|
|
431
|
+
`);
|
|
414
432
|
ctx.stdout("Initialized Dust repository in .dust/");
|
|
415
433
|
ctx.stdout(`Created directories: ${DUST_DIRECTORIES.join(", ")}`);
|
|
416
434
|
ctx.stdout("Created initial fact: .dust/facts/use-dust-for-planning.md");
|
|
435
|
+
ctx.stdout("Created settings: .dust/config/settings.json");
|
|
417
436
|
}
|
|
418
437
|
const claudeMdPath = `${ctx.cwd}/CLAUDE.md`;
|
|
419
438
|
if (fs.exists(claudeMdPath)) {
|
|
@@ -431,16 +450,23 @@ async function init(ctx, fs, _args) {
|
|
|
431
450
|
await fs.writeFile(agentsMdPath, agentsContent);
|
|
432
451
|
ctx.stdout("Created AGENTS.md with agent instructions");
|
|
433
452
|
}
|
|
453
|
+
const runner = dustCommand.split(" ")[0];
|
|
454
|
+
ctx.stdout("");
|
|
455
|
+
ctx.stdout("Commit the changes if you are happy, then get planning!");
|
|
456
|
+
ctx.stdout("");
|
|
457
|
+
ctx.stdout("If this is a new repository, you can start adding ideas or tasks right away:");
|
|
458
|
+
ctx.stdout(`> ${runner} claude "Idea: friendly UI for non-technical users"`);
|
|
459
|
+
ctx.stdout(`> ${runner} codex "Task: set up code coverage"`);
|
|
460
|
+
ctx.stdout("");
|
|
461
|
+
ctx.stdout("If this is an existing codebase, you might want to backfill goals and facts:");
|
|
462
|
+
ctx.stdout(`> ${runner} claude "Add goals and facts based on the code in this repository"`);
|
|
434
463
|
return { exitCode: 0 };
|
|
435
464
|
}
|
|
436
465
|
|
|
437
|
-
// lib/cli/list.ts
|
|
466
|
+
// lib/cli/commands/list.ts
|
|
438
467
|
var VALID_TYPES = ["tasks", "ideas", "goals", "facts"];
|
|
439
|
-
function
|
|
440
|
-
const
|
|
441
|
-
return match ? match[1].trim() : null;
|
|
442
|
-
}
|
|
443
|
-
async function list(ctx, fs, args) {
|
|
468
|
+
async function list(deps) {
|
|
469
|
+
const { arguments: args, context: ctx, fileSystem: fs } = deps;
|
|
444
470
|
const dustPath = `${ctx.cwd}/.dust`;
|
|
445
471
|
if (!fs.exists(dustPath)) {
|
|
446
472
|
ctx.stderr("Error: .dust directory not found");
|
|
@@ -480,11 +506,7 @@ async function list(ctx, fs, args) {
|
|
|
480
506
|
return { exitCode: 0 };
|
|
481
507
|
}
|
|
482
508
|
|
|
483
|
-
// lib/cli/next.ts
|
|
484
|
-
function extractTitle2(content) {
|
|
485
|
-
const match = content.match(/^#\s+(.+)$/m);
|
|
486
|
-
return match ? match[1].trim() : null;
|
|
487
|
-
}
|
|
509
|
+
// lib/cli/commands/next.ts
|
|
488
510
|
function extractBlockedBy(content) {
|
|
489
511
|
const blockedByMatch = content.match(/^## Blocked by\s*\n([\s\S]*?)(?=\n## |\n*$)/m);
|
|
490
512
|
if (!blockedByMatch) {
|
|
@@ -503,7 +525,8 @@ function extractBlockedBy(content) {
|
|
|
503
525
|
}
|
|
504
526
|
return blockers;
|
|
505
527
|
}
|
|
506
|
-
async function next(
|
|
528
|
+
async function next(deps) {
|
|
529
|
+
const { context: ctx, fileSystem: fs } = deps;
|
|
507
530
|
const dustPath = `${ctx.cwd}/.dust`;
|
|
508
531
|
if (!fs.exists(dustPath)) {
|
|
509
532
|
ctx.stderr("Error: .dust directory not found");
|
|
@@ -527,9 +550,9 @@ async function next(ctx, fs, _args) {
|
|
|
527
550
|
const blockers = extractBlockedBy(content);
|
|
528
551
|
const hasIncompleteBlocker = blockers.some((blocker) => existingTasks.has(blocker));
|
|
529
552
|
if (!hasIncompleteBlocker) {
|
|
530
|
-
const title =
|
|
531
|
-
const
|
|
532
|
-
unblockedTasks.push({
|
|
553
|
+
const title = extractTitle(content);
|
|
554
|
+
const relativePath = `.dust/tasks/${file}`;
|
|
555
|
+
unblockedTasks.push({ path: relativePath, title });
|
|
533
556
|
}
|
|
534
557
|
}
|
|
535
558
|
if (unblockedTasks.length === 0) {
|
|
@@ -538,9 +561,9 @@ async function next(ctx, fs, _args) {
|
|
|
538
561
|
ctx.stdout("Next tasks:");
|
|
539
562
|
for (const task of unblockedTasks) {
|
|
540
563
|
if (task.title) {
|
|
541
|
-
ctx.stdout(` ${task.
|
|
564
|
+
ctx.stdout(` ${task.path} - ${task.title}`);
|
|
542
565
|
} else {
|
|
543
|
-
ctx.stdout(` ${task.
|
|
566
|
+
ctx.stdout(` ${task.path}`);
|
|
544
567
|
}
|
|
545
568
|
}
|
|
546
569
|
return { exitCode: 0 };
|
|
@@ -566,22 +589,22 @@ function isHelpRequest(command) {
|
|
|
566
589
|
function isValidCommand(command) {
|
|
567
590
|
return COMMANDS.includes(command);
|
|
568
591
|
}
|
|
569
|
-
async function runCommand(command,
|
|
592
|
+
async function runCommand(command, deps) {
|
|
570
593
|
switch (command) {
|
|
571
594
|
case "init":
|
|
572
|
-
return init(
|
|
595
|
+
return init(deps);
|
|
573
596
|
case "validate":
|
|
574
|
-
return validate(
|
|
597
|
+
return validate(deps);
|
|
575
598
|
case "list":
|
|
576
|
-
return list(
|
|
599
|
+
return list(deps);
|
|
577
600
|
case "next":
|
|
578
|
-
return next(
|
|
601
|
+
return next(deps);
|
|
579
602
|
case "check":
|
|
580
|
-
return check(
|
|
603
|
+
return check(deps);
|
|
581
604
|
case "agent":
|
|
582
|
-
return agent(
|
|
605
|
+
return agent(deps);
|
|
583
606
|
case "help":
|
|
584
|
-
|
|
607
|
+
deps.context.stdout(generateHelpText(deps.settings));
|
|
585
608
|
return { exitCode: 0 };
|
|
586
609
|
}
|
|
587
610
|
}
|
|
@@ -600,7 +623,14 @@ async function main(options) {
|
|
|
600
623
|
ctx.stderr(`Run '${settings.dustCommand} help' for available commands`);
|
|
601
624
|
return { exitCode: 1 };
|
|
602
625
|
}
|
|
603
|
-
|
|
626
|
+
const deps = {
|
|
627
|
+
arguments: commandArgs,
|
|
628
|
+
context: ctx,
|
|
629
|
+
fileSystem: fs,
|
|
630
|
+
globScanner: glob,
|
|
631
|
+
settings
|
|
632
|
+
};
|
|
633
|
+
return runCommand(command, deps);
|
|
604
634
|
}
|
|
605
635
|
|
|
606
636
|
// lib/cli/entry.ts
|
package/package.json
CHANGED