@joshski/dust 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dust.js
CHANGED
|
@@ -162,23 +162,21 @@ function createHooksManager(cwd, fileSystem, settings) {
|
|
|
162
162
|
return {
|
|
163
163
|
isGitRepo: () => fileSystem.exists(gitDir),
|
|
164
164
|
isHookInstalled: async () => {
|
|
165
|
-
if (!fileSystem.exists(prePushPath)) {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
165
|
try {
|
|
169
166
|
const content = await fileSystem.readFile(prePushPath);
|
|
170
167
|
return content.includes(DUST_HOOK_START);
|
|
171
|
-
} catch {
|
|
172
|
-
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error.code === "ENOENT") {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
throw error;
|
|
173
173
|
}
|
|
174
174
|
},
|
|
175
175
|
installHook: async () => {
|
|
176
|
-
|
|
177
|
-
await fileSystem.mkdir(hooksDir, { recursive: true });
|
|
178
|
-
}
|
|
176
|
+
await fileSystem.mkdir(hooksDir, { recursive: true });
|
|
179
177
|
const hookContent = generateHookContent(settings.dustCommand);
|
|
180
178
|
let finalContent;
|
|
181
|
-
|
|
179
|
+
try {
|
|
182
180
|
const existingContent = await fileSystem.readFile(prePushPath);
|
|
183
181
|
if (existingContent.includes(DUST_HOOK_START)) {
|
|
184
182
|
const withoutDust = removeDustSection(existingContent);
|
|
@@ -195,19 +193,20 @@ ${hookContent}
|
|
|
195
193
|
${hookContent}
|
|
196
194
|
`;
|
|
197
195
|
}
|
|
198
|
-
}
|
|
199
|
-
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (error.code === "ENOENT") {
|
|
198
|
+
finalContent = `#!/bin/sh
|
|
200
199
|
|
|
201
200
|
${hookContent}
|
|
202
201
|
`;
|
|
202
|
+
} else {
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
203
205
|
}
|
|
204
206
|
await fileSystem.writeFile(prePushPath, finalContent);
|
|
205
207
|
await fileSystem.chmod(prePushPath, 493);
|
|
206
208
|
},
|
|
207
209
|
getHookBinaryPath: async () => {
|
|
208
|
-
if (!fileSystem.exists(prePushPath)) {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
210
|
try {
|
|
212
211
|
const content = await fileSystem.readFile(prePushPath);
|
|
213
212
|
const dustSection = extractDustSection(content);
|
|
@@ -216,15 +215,23 @@ ${hookContent}
|
|
|
216
215
|
}
|
|
217
216
|
const match = dustSection.match(/^(.+) pre push$/m);
|
|
218
217
|
return match ? match[1] : null;
|
|
219
|
-
} catch {
|
|
220
|
-
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error.code === "ENOENT") {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
221
223
|
}
|
|
222
224
|
},
|
|
223
225
|
updateHookBinaryPath: async (newPath) => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
let content;
|
|
227
|
+
try {
|
|
228
|
+
content = await fileSystem.readFile(prePushPath);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error.code === "ENOENT") {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
226
234
|
}
|
|
227
|
-
const content = await fileSystem.readFile(prePushPath);
|
|
228
235
|
const dustSection = extractDustSection(content);
|
|
229
236
|
if (!dustSection) {
|
|
230
237
|
return;
|
|
@@ -657,34 +664,63 @@ function validateNoCycles(allGoalRelationships) {
|
|
|
657
664
|
}
|
|
658
665
|
return violations;
|
|
659
666
|
}
|
|
667
|
+
async function safeScanDir(glob, dirPath) {
|
|
668
|
+
const files = [];
|
|
669
|
+
try {
|
|
670
|
+
for await (const file of glob.scan(dirPath)) {
|
|
671
|
+
files.push(file);
|
|
672
|
+
}
|
|
673
|
+
return { files, exists: true };
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (error.code === "ENOENT") {
|
|
676
|
+
return { files: [], exists: false };
|
|
677
|
+
}
|
|
678
|
+
throw error;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
660
681
|
async function lintMarkdown(dependencies) {
|
|
661
682
|
const { context, fileSystem, globScanner: glob } = dependencies;
|
|
662
683
|
const dustPath = `${context.cwd}/.dust`;
|
|
663
|
-
|
|
684
|
+
const dustScan = await safeScanDir(glob, dustPath);
|
|
685
|
+
if (!dustScan.exists) {
|
|
664
686
|
context.stderr("Error: .dust directory not found");
|
|
665
687
|
context.stderr("Run 'dust init' to initialize a Dust repository");
|
|
666
688
|
return { exitCode: 1 };
|
|
667
689
|
}
|
|
690
|
+
const dustFiles = dustScan.files;
|
|
668
691
|
const violations = [];
|
|
669
692
|
context.stdout("Validating links in .dust/...");
|
|
670
|
-
for
|
|
693
|
+
for (const file of dustFiles) {
|
|
671
694
|
if (!file.endsWith(".md"))
|
|
672
695
|
continue;
|
|
673
696
|
const filePath = `${dustPath}/${file}`;
|
|
674
|
-
|
|
675
|
-
|
|
697
|
+
try {
|
|
698
|
+
const content = await fileSystem.readFile(filePath);
|
|
699
|
+
violations.push(...validateLinks(filePath, content, fileSystem));
|
|
700
|
+
} catch (error) {
|
|
701
|
+
if (error.code !== "ENOENT") {
|
|
702
|
+
throw error;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
676
705
|
}
|
|
677
706
|
const contentDirs = ["goals", "facts", "ideas", "tasks"];
|
|
678
707
|
context.stdout("Validating content files...");
|
|
679
708
|
for (const dir of contentDirs) {
|
|
680
709
|
const dirPath = `${dustPath}/${dir}`;
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
for await (const file of glob.scan(dirPath)) {
|
|
710
|
+
const { files } = await safeScanDir(glob, dirPath);
|
|
711
|
+
for (const file of files) {
|
|
684
712
|
if (!file.endsWith(".md"))
|
|
685
713
|
continue;
|
|
686
714
|
const filePath = `${dirPath}/${file}`;
|
|
687
|
-
|
|
715
|
+
let content;
|
|
716
|
+
try {
|
|
717
|
+
content = await fileSystem.readFile(filePath);
|
|
718
|
+
} catch (error) {
|
|
719
|
+
if (error.code === "ENOENT") {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
throw error;
|
|
723
|
+
}
|
|
688
724
|
const openingSentenceViolation = validateOpeningSentence(filePath, content);
|
|
689
725
|
if (openingSentenceViolation) {
|
|
690
726
|
violations.push(openingSentenceViolation);
|
|
@@ -700,13 +736,22 @@ async function lintMarkdown(dependencies) {
|
|
|
700
736
|
}
|
|
701
737
|
}
|
|
702
738
|
const tasksPath = `${dustPath}/tasks`;
|
|
703
|
-
|
|
739
|
+
const { files: taskFiles } = await safeScanDir(glob, tasksPath);
|
|
740
|
+
if (taskFiles.length > 0) {
|
|
704
741
|
context.stdout("Validating task files in .dust/tasks/...");
|
|
705
|
-
for
|
|
742
|
+
for (const file of taskFiles) {
|
|
706
743
|
if (!file.endsWith(".md"))
|
|
707
744
|
continue;
|
|
708
745
|
const filePath = `${tasksPath}/${file}`;
|
|
709
|
-
|
|
746
|
+
let content;
|
|
747
|
+
try {
|
|
748
|
+
content = await fileSystem.readFile(filePath);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
if (error.code === "ENOENT") {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
throw error;
|
|
754
|
+
}
|
|
710
755
|
const filenameViolation = validateFilename(filePath);
|
|
711
756
|
if (filenameViolation) {
|
|
712
757
|
violations.push(filenameViolation);
|
|
@@ -716,14 +761,23 @@ async function lintMarkdown(dependencies) {
|
|
|
716
761
|
}
|
|
717
762
|
}
|
|
718
763
|
const goalsPath = `${dustPath}/goals`;
|
|
719
|
-
|
|
764
|
+
const { files: goalFiles } = await safeScanDir(glob, goalsPath);
|
|
765
|
+
if (goalFiles.length > 0) {
|
|
720
766
|
context.stdout("Validating goal hierarchy in .dust/goals/...");
|
|
721
767
|
const allGoalRelationships = [];
|
|
722
|
-
for
|
|
768
|
+
for (const file of goalFiles) {
|
|
723
769
|
if (!file.endsWith(".md"))
|
|
724
770
|
continue;
|
|
725
771
|
const filePath = `${goalsPath}/${file}`;
|
|
726
|
-
|
|
772
|
+
let content;
|
|
773
|
+
try {
|
|
774
|
+
content = await fileSystem.readFile(filePath);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (error.code === "ENOENT") {
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
727
781
|
violations.push(...validateGoalHierarchySections(filePath, content));
|
|
728
782
|
violations.push(...validateGoalHierarchyLinks(filePath, content));
|
|
729
783
|
allGoalRelationships.push(extractGoalRelationships(filePath, content));
|
|
@@ -934,37 +988,59 @@ async function init(dependencies) {
|
|
|
934
988
|
const dustPath = `${context.cwd}/.dust`;
|
|
935
989
|
const dustCommand = detectDustCommand(context.cwd, fileSystem);
|
|
936
990
|
const agentInstruction = `Always immediately run \`${dustCommand} agent\` when you start working in this repository.`;
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
991
|
+
await fileSystem.mkdir(dustPath, { recursive: true });
|
|
992
|
+
for (const dir of DUST_DIRECTORIES) {
|
|
993
|
+
await fileSystem.mkdir(`${dustPath}/${dir}`, { recursive: true });
|
|
994
|
+
}
|
|
995
|
+
let dustDirCreated = false;
|
|
996
|
+
try {
|
|
997
|
+
await fileSystem.writeFile(`${dustPath}/facts/use-dust-for-planning.md`, USE_DUST_FACT, { flag: "wx" });
|
|
998
|
+
dustDirCreated = true;
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
if (error.code !== "EEXIST") {
|
|
1001
|
+
throw error;
|
|
943
1002
|
}
|
|
944
|
-
|
|
1003
|
+
}
|
|
1004
|
+
try {
|
|
945
1005
|
const settings = generateSettings(context.cwd, fileSystem);
|
|
946
1006
|
await fileSystem.writeFile(`${dustPath}/config/settings.json`, `${JSON.stringify(settings, null, 2)}
|
|
947
|
-
|
|
1007
|
+
`, { flag: "wx" });
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (error.code !== "EEXIST") {
|
|
1010
|
+
throw error;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (dustDirCreated) {
|
|
948
1014
|
context.stdout(`${colors.green}✨ Initialized${colors.reset} Dust repository in ${colors.cyan}.dust/${colors.reset}`);
|
|
949
1015
|
context.stdout(`${colors.green}\uD83D\uDCC1 Created directories:${colors.reset} ${colors.dim}${DUST_DIRECTORIES.join(", ")}${colors.reset}`);
|
|
950
1016
|
context.stdout(`${colors.green}\uD83D\uDCC4 Created initial fact:${colors.reset} ${colors.cyan}.dust/facts/use-dust-for-planning.md${colors.reset}`);
|
|
951
1017
|
context.stdout(`${colors.green}⚙️ Created settings:${colors.reset} ${colors.cyan}.dust/config/settings.json${colors.reset}`);
|
|
1018
|
+
} else {
|
|
1019
|
+
context.stdout(`${colors.yellow}\uD83D\uDCE6 Note:${colors.reset} ${colors.cyan}.dust${colors.reset} directory already exists, skipping creation`);
|
|
952
1020
|
}
|
|
953
1021
|
const claudeMdPath = `${context.cwd}/CLAUDE.md`;
|
|
954
|
-
|
|
955
|
-
context.stdout(`${colors.yellow}⚠️ Warning:${colors.reset} ${colors.cyan}CLAUDE.md${colors.reset} already exists. Consider adding: ${colors.dim}"${agentInstruction}"${colors.reset}`);
|
|
956
|
-
} else {
|
|
1022
|
+
try {
|
|
957
1023
|
const claudeContent = loadTemplate("claude-md", { dustCommand });
|
|
958
|
-
await fileSystem.writeFile(claudeMdPath, claudeContent);
|
|
1024
|
+
await fileSystem.writeFile(claudeMdPath, claudeContent, { flag: "wx" });
|
|
959
1025
|
context.stdout(`${colors.green}\uD83D\uDCC4 Created${colors.reset} ${colors.cyan}CLAUDE.md${colors.reset} with agent instructions`);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
if (error.code === "EEXIST") {
|
|
1028
|
+
context.stdout(`${colors.yellow}⚠️ Warning:${colors.reset} ${colors.cyan}CLAUDE.md${colors.reset} already exists. Consider adding: ${colors.dim}"${agentInstruction}"${colors.reset}`);
|
|
1029
|
+
} else {
|
|
1030
|
+
throw error;
|
|
1031
|
+
}
|
|
960
1032
|
}
|
|
961
1033
|
const agentsMdPath = `${context.cwd}/AGENTS.md`;
|
|
962
|
-
|
|
963
|
-
context.stdout(`${colors.yellow}⚠️ Warning:${colors.reset} ${colors.cyan}AGENTS.md${colors.reset} already exists. Consider adding: ${colors.dim}"${agentInstruction}"${colors.reset}`);
|
|
964
|
-
} else {
|
|
1034
|
+
try {
|
|
965
1035
|
const agentsContent = loadTemplate("agents-md", { dustCommand });
|
|
966
|
-
await fileSystem.writeFile(agentsMdPath, agentsContent);
|
|
1036
|
+
await fileSystem.writeFile(agentsMdPath, agentsContent, { flag: "wx" });
|
|
967
1037
|
context.stdout(`${colors.green}\uD83D\uDCC4 Created${colors.reset} ${colors.cyan}AGENTS.md${colors.reset} with agent instructions`);
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
if (error.code === "EEXIST") {
|
|
1040
|
+
context.stdout(`${colors.yellow}⚠️ Warning:${colors.reset} ${colors.cyan}AGENTS.md${colors.reset} already exists. Consider adding: ${colors.dim}"${agentInstruction}"${colors.reset}`);
|
|
1041
|
+
} else {
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
968
1044
|
}
|
|
969
1045
|
const runner = dustCommand.split(" ")[0];
|
|
970
1046
|
context.stdout("");
|
|
@@ -1608,13 +1684,14 @@ function formatEvent(event) {
|
|
|
1608
1684
|
case "loop.started":
|
|
1609
1685
|
return `\uD83D\uDD04 Starting dust loop claude (max ${event.maxIterations} iterations)...`;
|
|
1610
1686
|
case "loop.syncing":
|
|
1611
|
-
return "\
|
|
1687
|
+
return "\uD83C\uDF0D Syncing with remote";
|
|
1612
1688
|
case "loop.sync_skipped":
|
|
1613
1689
|
return `Note: git pull skipped (${event.reason})`;
|
|
1614
1690
|
case "loop.checking_tasks":
|
|
1615
|
-
return
|
|
1691
|
+
return null;
|
|
1616
1692
|
case "loop.no_tasks":
|
|
1617
|
-
return
|
|
1693
|
+
return `\uD83D\uDE34 No tasks available. Sleeping...
|
|
1694
|
+
`;
|
|
1618
1695
|
case "loop.tasks_found":
|
|
1619
1696
|
return "✨ Found task(s). \uD83E\uDD16 Starting Claude...";
|
|
1620
1697
|
case "claude.started":
|
|
@@ -2066,7 +2143,10 @@ function createFileSystem(primitives) {
|
|
|
2066
2143
|
return {
|
|
2067
2144
|
exists: primitives.existsSync,
|
|
2068
2145
|
readFile: (path) => primitives.readFile(path, "utf-8"),
|
|
2069
|
-
writeFile: (path, content) => primitives.writeFile(path, content,
|
|
2146
|
+
writeFile: (path, content, options) => primitives.writeFile(path, content, {
|
|
2147
|
+
encoding: "utf-8",
|
|
2148
|
+
flag: options?.flag
|
|
2149
|
+
}),
|
|
2070
2150
|
mkdir: async (path, options) => {
|
|
2071
2151
|
await primitives.mkdir(path, options);
|
|
2072
2152
|
},
|
package/package.json
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
{{/if}}{{#unless isClaudeCodeWeb}}Follow these steps:
|
|
5
5
|
{{/unless}}
|
|
6
6
|
1. Run `{{bin}} next` to identify the (unblocked) task the user is referring to
|
|
7
|
-
2. Run `{{bin}}
|
|
8
|
-
3.
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
2. Run `{{bin}} focus "<task name>"` (so everyone knows you're working on it)
|
|
8
|
+
3. Run `{{bin}} check` to verify the project is in a good state
|
|
9
|
+
4. Implement the task
|
|
10
|
+
{{#unless hooksInstalled}}5. Run `{{bin}} check` before committing
|
|
11
|
+
6.{{/unless}}{{#if hooksInstalled}}5.{{/if}} Create a single atomic commit that includes:
|
|
11
12
|
- All implementation changes
|
|
12
13
|
- Deletion of the completed task file
|
|
13
14
|
- Updates to any facts that changed
|
|
@@ -20,6 +21,6 @@
|
|
|
20
21
|
Add validation for user input
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
{{#unless hooksInstalled}}
|
|
24
|
+
{{#unless hooksInstalled}}7.{{/unless}}{{#if hooksInstalled}}6.{{/if}} Push your commit to the remote repository
|
|
24
25
|
|
|
25
26
|
Keep your change small and focused. One task, one commit.
|
|
@@ -4,4 +4,4 @@ Follow these steps:
|
|
|
4
4
|
|
|
5
5
|
1. Run `{{bin}} next` to see available tasks
|
|
6
6
|
2. Pick ONE task and read its file to understand the requirements
|
|
7
|
-
3. Run `{{bin}}
|
|
7
|
+
3. Run `{{bin}} implement task` for instructions about how to implement
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
{{/if}}{{#unless isClaudeCodeWeb}}Follow these steps:
|
|
5
5
|
{{/unless}}
|
|
6
6
|
1. Run `{{bin}} next` to identify the (unblocked) task the user is referring to
|
|
7
|
-
2. Run `{{bin}}
|
|
8
|
-
3.
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
2. Run `{{bin}} focus "<task name>"` (so everyone knows you're working on it)
|
|
8
|
+
3. Run `{{bin}} check` to verify the project is in a good state
|
|
9
|
+
4. Implement the task
|
|
10
|
+
{{#unless hooksInstalled}}5. Run `{{bin}} check` before committing
|
|
11
|
+
6.{{/unless}}{{#if hooksInstalled}}5.{{/if}} Create a single atomic commit that includes:
|
|
11
12
|
- All implementation changes
|
|
12
13
|
- Deletion of the completed task file
|
|
13
14
|
- Updates to any facts that changed
|
|
@@ -20,6 +21,6 @@
|
|
|
20
21
|
Add validation for user input
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
{{#unless hooksInstalled}}
|
|
24
|
+
{{#unless hooksInstalled}}7.{{/unless}}{{#if hooksInstalled}}6.{{/if}} Push your commit to the remote repository
|
|
24
25
|
|
|
25
26
|
Keep your change small and focused. One task, one commit.
|
|
@@ -4,4 +4,4 @@ Follow these steps:
|
|
|
4
4
|
|
|
5
5
|
1. Run `{{bin}} next` to see available tasks
|
|
6
6
|
2. Pick ONE task and read its file to understand the requirements
|
|
7
|
-
3. Run `{{bin}}
|
|
7
|
+
3. Run `{{bin}} implement task` for instructions about how to implement
|