@joshski/dust 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/dust.js +142 -87
  2. package/package.json +1 -1
package/dist/dust.js CHANGED
@@ -2,11 +2,81 @@
2
2
  import { existsSync } from "node:fs";
3
3
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
4
4
 
5
+ // lib/cli/templates.ts
6
+ import { readFileSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
10
+ var templatesDir = join(__dirname2, "../templates");
11
+ function loadTemplate(name, variables = {}) {
12
+ const templatePath = join(templatesDir, `${name}.txt`);
13
+ let content = readFileSync(templatePath, "utf-8");
14
+ for (const [key, value] of Object.entries(variables)) {
15
+ content = content.replaceAll(`{{${key}}}`, value);
16
+ }
17
+ return content;
18
+ }
19
+
20
+ // lib/cli/agent.ts
21
+ var AGENT_SUBCOMMANDS = [
22
+ "work",
23
+ "tasks",
24
+ "goals",
25
+ "ideas",
26
+ "help"
27
+ ];
28
+ function generateAgentGreeting(settings) {
29
+ return loadTemplate("agent-greeting", { bin: settings.binaryPath });
30
+ }
31
+ function generateWorkInstructions(settings) {
32
+ return loadTemplate("agent-work", { bin: settings.binaryPath });
33
+ }
34
+ function generateTasksInstructions(settings) {
35
+ return loadTemplate("agent-tasks", { bin: settings.binaryPath });
36
+ }
37
+ function generateGoalsInstructions(settings) {
38
+ return loadTemplate("agent-goals", { bin: settings.binaryPath });
39
+ }
40
+ function generateIdeasInstructions(settings) {
41
+ return loadTemplate("agent-ideas", { bin: settings.binaryPath });
42
+ }
43
+ function generateAgentHelp(settings) {
44
+ return loadTemplate("agent-help", { bin: settings.binaryPath });
45
+ }
46
+ async function agent(ctx, args, settings) {
47
+ const subcommand = args[0];
48
+ if (!subcommand) {
49
+ ctx.stdout(generateAgentGreeting(settings));
50
+ return { exitCode: 0 };
51
+ }
52
+ switch (subcommand) {
53
+ case "work":
54
+ ctx.stdout(generateWorkInstructions(settings));
55
+ return { exitCode: 0 };
56
+ case "tasks":
57
+ ctx.stdout(generateTasksInstructions(settings));
58
+ return { exitCode: 0 };
59
+ case "goals":
60
+ ctx.stdout(generateGoalsInstructions(settings));
61
+ return { exitCode: 0 };
62
+ case "ideas":
63
+ ctx.stdout(generateIdeasInstructions(settings));
64
+ return { exitCode: 0 };
65
+ case "help":
66
+ ctx.stdout(generateAgentHelp(settings));
67
+ return { exitCode: 0 };
68
+ default:
69
+ ctx.stderr(`Unknown subcommand: ${subcommand}`);
70
+ ctx.stderr(`Available: ${AGENT_SUBCOMMANDS.join(", ")}`);
71
+ return { exitCode: 1 };
72
+ }
73
+ }
74
+
5
75
  // lib/cli/check.ts
6
76
  import { spawn } from "node:child_process";
7
77
 
8
78
  // lib/cli/validate.ts
9
- import { dirname, resolve } from "node:path";
79
+ import { dirname as dirname2, resolve } from "node:path";
10
80
  var REQUIRED_HEADINGS = ["## Goals", "## Blocked by", "## Definition of done"];
11
81
  var SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*\.md$/;
12
82
  function validateFilename(filePath) {
@@ -36,7 +106,7 @@ function validateLinks(filePath, content, fs) {
36
106
  const violations = [];
37
107
  const lines = content.split(`
38
108
  `);
39
- const fileDir = dirname(filePath);
109
+ const fileDir = dirname2(filePath);
40
110
  for (let i = 0;i < lines.length; i++) {
41
111
  const line = lines[i];
42
112
  const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
@@ -59,6 +129,69 @@ function validateLinks(filePath, content, fs) {
59
129
  }
60
130
  return violations;
61
131
  }
132
+ var SEMANTIC_RULES = [
133
+ {
134
+ section: "## Goals",
135
+ requiredPath: "/.dust/goals/",
136
+ description: "goal"
137
+ },
138
+ {
139
+ section: "## Blocked by",
140
+ requiredPath: "/.dust/tasks/",
141
+ description: "task"
142
+ }
143
+ ];
144
+ function validateSemanticLinks(filePath, content) {
145
+ const violations = [];
146
+ const lines = content.split(`
147
+ `);
148
+ const fileDir = dirname2(filePath);
149
+ let currentSection = null;
150
+ for (let i = 0;i < lines.length; i++) {
151
+ const line = lines[i];
152
+ if (line.startsWith("## ")) {
153
+ currentSection = line;
154
+ continue;
155
+ }
156
+ const rule = SEMANTIC_RULES.find((r) => r.section === currentSection);
157
+ if (!rule)
158
+ continue;
159
+ const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
160
+ let match = linkPattern.exec(line);
161
+ while (match) {
162
+ const linkTarget = match[2];
163
+ if (linkTarget.startsWith("#")) {
164
+ violations.push({
165
+ file: filePath,
166
+ message: `Link in "${rule.section}" must point to a ${rule.description} file, not an anchor: "${linkTarget}"`,
167
+ line: i + 1
168
+ });
169
+ match = linkPattern.exec(line);
170
+ continue;
171
+ }
172
+ if (linkTarget.startsWith("http://") || linkTarget.startsWith("https://")) {
173
+ violations.push({
174
+ file: filePath,
175
+ message: `Link in "${rule.section}" must point to a ${rule.description} file, not an external URL: "${linkTarget}"`,
176
+ line: i + 1
177
+ });
178
+ match = linkPattern.exec(line);
179
+ continue;
180
+ }
181
+ const targetPath = linkTarget.split("#")[0];
182
+ const resolvedPath = resolve(fileDir, targetPath);
183
+ if (!resolvedPath.includes(rule.requiredPath)) {
184
+ violations.push({
185
+ file: filePath,
186
+ message: `Link in "${rule.section}" must point to a ${rule.description} file: "${linkTarget}"`,
187
+ line: i + 1
188
+ });
189
+ }
190
+ match = linkPattern.exec(line);
191
+ }
192
+ }
193
+ return violations;
194
+ }
62
195
  async function validate(ctx, fs, _args, glob) {
63
196
  const dustPath = `${ctx.cwd}/.dust`;
64
197
  if (!fs.exists(dustPath)) {
@@ -88,6 +221,7 @@ async function validate(ctx, fs, _args, glob) {
88
221
  violations.push(filenameViolation);
89
222
  }
90
223
  violations.push(...validateTaskHeadings(filePath, content));
224
+ violations.push(...validateSemanticLinks(filePath, content));
91
225
  }
92
226
  }
93
227
  if (violations.length === 0) {
@@ -308,12 +442,12 @@ async function prompt(ctx, fs, args) {
308
442
  }
309
443
 
310
444
  // lib/cli/settings.ts
311
- import { join } from "node:path";
445
+ import { join as join2 } from "node:path";
312
446
  var DEFAULT_SETTINGS = {
313
447
  binaryPath: "dust"
314
448
  };
315
449
  async function loadSettings(cwd, fs) {
316
- const settingsPath = join(cwd, ".dust", "config", "settings.json");
450
+ const settingsPath = join2(cwd, ".dust", "config", "settings.json");
317
451
  if (!fs.exists(settingsPath)) {
318
452
  return DEFAULT_SETTINGS;
319
453
  }
@@ -337,92 +471,11 @@ var COMMANDS = [
337
471
  "list",
338
472
  "next",
339
473
  "check",
474
+ "agent",
340
475
  "help"
341
476
  ];
342
477
  function generateHelpText(settings) {
343
- const bin = settings.binaryPath;
344
- return `dust - A lightweight planning system for human-AI collaboration
345
-
346
- Usage: ${bin} <command> [options]
347
-
348
- Commands:
349
- init Initialize a new Dust repository
350
- prompt <name> Output a prompt by name (e.g., ${bin} prompt work)
351
- validate Run validation checks on .dust/ files
352
- list [type] List items (tasks, ideas, goals, facts)
353
- next Show tasks ready to work on (not blocked)
354
- check Run project-defined quality gate hook
355
- help Show this help message
356
-
357
- Examples:
358
- ${bin} init
359
- ${bin} prompt work
360
- ${bin} validate
361
- ${bin} list tasks
362
- ${bin} list
363
- ${bin} next
364
- ${bin} check
365
-
366
- ---
367
-
368
- ## Agent Guide
369
-
370
- This section provides comprehensive guidance for AI agents working with dust.
371
-
372
- ### Directory Structure
373
-
374
- The \`.dust/\` directory contains all planning artifacts:
375
-
376
- - **\`.dust/goals/\`** - Mission statements and guiding principles
377
- - **\`.dust/ideas/\`** - Future feature notes and proposals (intentionally vague)
378
- - **\`.dust/tasks/\`** - Detailed work plans with dependencies and definitions of done
379
- - **\`.dust/facts/\`** - Documentation of current system state and architecture
380
- - **\`.dust/hooks/\`** - Executable scripts for quality gates (e.g., \`check\` hook)
381
-
382
- All files are markdown with slug-style names (lowercase, hyphens, no spaces).
383
-
384
- ### Working on Tasks
385
-
386
- **Run \`${bin} check\` before starting work** to verify the project is in a good state before making changes.
387
-
388
- Run \`${bin} next\` to find tasks ready to work on. Each task file contains:
389
-
390
- - \`## Goals\` - Links to goals this task supports
391
- - \`## Blocked by\` - Tasks that must complete first (empty or "(none)" means ready)
392
- - \`## Definition of done\` - Criteria for completion
393
-
394
- A task is **unblocked** when its "Blocked by" section is empty, says "(none)", or all referenced task files have been deleted.
395
-
396
- ### Completing a Task
397
-
398
- **Run \`${bin} check\` before committing** to ensure all quality gates pass.
399
-
400
- When finishing a task, create a single atomic commit that includes:
401
-
402
- 1. All implementation changes
403
- 2. Deletion of the completed task file
404
- 3. Updates to any facts that changed
405
- 4. Deletion of any ideas that were fully realized
406
- 5. Updates to any tasks that referenced this one in their "Blocked by" sections
407
-
408
- ### Common Workflows
409
-
410
- - **"Work on the next task"** - Run \`${bin} next\`, pick a task, implement it
411
- - **"Work on task X"** - Implement \`.dust/tasks/X.md\` directly
412
- - **"Convert idea Y to tasks"** - Break down \`.dust/ideas/Y.md\` into tasks
413
- - **"Validate facts"** - Check \`.dust/facts/\` for accuracy against the codebase
414
-
415
- ### Configuring Agent Files
416
-
417
- Projects using dust should add a minimal pointer to their agent configuration files (CLAUDE.md, AGENTS.md, etc.):
418
-
419
- \`\`\`markdown
420
- This project uses [dust](https://github.com/joshski/dust) for planning and documentation.
421
- Always run \`dust help\` when you start working in this repository.
422
- \`\`\`
423
-
424
- This approach keeps agent instructions minimal, ensures agents get current documentation, and reduces maintenance burden.
425
- `;
478
+ return loadTemplate("help", { bin: settings.binaryPath });
426
479
  }
427
480
  var HELP_TEXT = generateHelpText({ binaryPath: "dust" });
428
481
  function isHelpRequest(command) {
@@ -445,6 +498,8 @@ async function runCommand(command, commandArgs, ctx, fs, glob, settings) {
445
498
  return next(ctx, fs, commandArgs);
446
499
  case "check":
447
500
  return check(ctx, fs, commandArgs, defaultProcessRunner, glob);
501
+ case "agent":
502
+ return agent(ctx, commandArgs, settings);
448
503
  case "help":
449
504
  ctx.stdout(generateHelpText(settings));
450
505
  return { exitCode: 0 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A lightweight planning system for human-AI collaboration",
5
5
  "type": "module",
6
6
  "bin": {