@joshski/dust 0.1.61 → 0.1.63
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/artifacts/facts.d.ts +1 -1
- package/dist/artifacts/ideas.d.ts +1 -1
- package/dist/artifacts/index.d.ts +4 -3
- package/dist/artifacts/principles.d.ts +1 -1
- package/dist/artifacts/tasks.d.ts +1 -1
- package/dist/artifacts/workflow-tasks.d.ts +1 -1
- package/dist/artifacts.js +29 -1
- package/dist/audits/index.d.ts +36 -0
- package/dist/audits/stock-audits.d.ts +12 -0
- package/dist/audits.js +459 -0
- package/dist/cli/dedent.d.ts +8 -0
- package/dist/cli/types.d.ts +2 -21
- package/dist/dust.js +70 -58
- package/dist/filesystem/types.d.ts +24 -0
- package/package.json +9 -2
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { FileSystem, ReadableFileSystem } from '../
|
|
1
|
+
import type { FileSystem, ReadableFileSystem } from '../filesystem/types';
|
|
2
2
|
import { type Fact } from './facts';
|
|
3
3
|
import { type Idea, type IdeaOpenQuestion, type IdeaOption, parseOpenQuestions } from './ideas';
|
|
4
4
|
import { type Principle } from './principles';
|
|
5
5
|
import { type Task } from './tasks';
|
|
6
|
-
import { type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch } from './workflow-tasks';
|
|
6
|
+
import { CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllCaptureIdeaTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch } from './workflow-tasks';
|
|
7
7
|
export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, };
|
|
8
|
-
export { parseOpenQuestions };
|
|
8
|
+
export { CAPTURE_IDEA_PREFIX, findAllCaptureIdeaTasks, parseOpenQuestions };
|
|
9
|
+
export type { IdeaInProgress };
|
|
9
10
|
export interface ArtifactsRepository {
|
|
10
11
|
parseIdea(options: {
|
|
11
12
|
slug: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FileSystem, ReadableFileSystem } from '../
|
|
1
|
+
import type { FileSystem, ReadableFileSystem } from '../filesystem/types';
|
|
2
2
|
export declare const IDEA_TRANSITION_PREFIXES: string[];
|
|
3
3
|
export declare const CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
4
4
|
export declare const BUILD_IDEA_PREFIX = "Build Idea: ";
|
package/dist/artifacts.js
CHANGED
|
@@ -273,6 +273,32 @@ async function parseTask(fileSystem, dustPath, slug) {
|
|
|
273
273
|
// lib/artifacts/workflow-tasks.ts
|
|
274
274
|
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
275
275
|
var BUILD_IDEA_PREFIX = "Build Idea: ";
|
|
276
|
+
async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
|
|
277
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
278
|
+
if (!fileSystem.exists(tasksPath))
|
|
279
|
+
return [];
|
|
280
|
+
const files = await fileSystem.readdir(tasksPath);
|
|
281
|
+
const results = [];
|
|
282
|
+
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
283
|
+
const content = await fileSystem.readFile(`${tasksPath}/${file}`);
|
|
284
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
285
|
+
if (!titleMatch)
|
|
286
|
+
continue;
|
|
287
|
+
const title = titleMatch[1].trim();
|
|
288
|
+
if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
|
|
289
|
+
results.push({
|
|
290
|
+
taskSlug: file.replace(/\.md$/, ""),
|
|
291
|
+
ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
|
|
292
|
+
});
|
|
293
|
+
} else if (title.startsWith(BUILD_IDEA_PREFIX)) {
|
|
294
|
+
results.push({
|
|
295
|
+
taskSlug: file.replace(/\.md$/, ""),
|
|
296
|
+
ideaTitle: title.slice(BUILD_IDEA_PREFIX.length)
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
276
302
|
function titleToFilename(title) {
|
|
277
303
|
return `${title.toLowerCase().replace(/\./g, "-").replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "")}.md`;
|
|
278
304
|
}
|
|
@@ -594,6 +620,8 @@ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
|
|
|
594
620
|
}
|
|
595
621
|
export {
|
|
596
622
|
parseOpenQuestions,
|
|
623
|
+
findAllCaptureIdeaTasks,
|
|
597
624
|
buildReadOnlyArtifactsRepository,
|
|
598
|
-
buildArtifactsRepository
|
|
625
|
+
buildArtifactsRepository,
|
|
626
|
+
CAPTURE_IDEA_PREFIX
|
|
599
627
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audits repository - programmatic access to audit templates.
|
|
3
|
+
*
|
|
4
|
+
* Audits are canned tasks that help maintain project health.
|
|
5
|
+
* Sources:
|
|
6
|
+
* 1. User-configured audits in .dust/config/audits/*.md (takes precedence)
|
|
7
|
+
* 2. Stock audits from lib/audits/stock-audits.ts
|
|
8
|
+
*/
|
|
9
|
+
import type { FileSystem } from '../filesystem/types';
|
|
10
|
+
export { loadStockAudits } from './stock-audits';
|
|
11
|
+
export interface Audit {
|
|
12
|
+
name: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description: string;
|
|
15
|
+
template: string;
|
|
16
|
+
source: 'stock' | string;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateAuditTaskResult {
|
|
19
|
+
filePath: string;
|
|
20
|
+
relativePath: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AuditsRepository {
|
|
23
|
+
listAudits(): Promise<Audit[]>;
|
|
24
|
+
parseAudit(options: {
|
|
25
|
+
name: string;
|
|
26
|
+
}): Promise<Audit>;
|
|
27
|
+
createAuditTask(options: {
|
|
28
|
+
name: string;
|
|
29
|
+
}): Promise<CreateAuditTaskResult>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Transforms audit template content for the task file.
|
|
33
|
+
* Changes the title from "# Original Title" to "# Audit: Original Title"
|
|
34
|
+
*/
|
|
35
|
+
export declare function transformAuditContent(content: string): string;
|
|
36
|
+
export declare function buildAuditsRepository(fileSystem: FileSystem, dustPath: string): AuditsRepository;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stock audit templates as type-safe functions.
|
|
3
|
+
*
|
|
4
|
+
* Users can override any of these by placing a file with the same name
|
|
5
|
+
* in .dust/config/audits/.
|
|
6
|
+
*/
|
|
7
|
+
export interface StockAudit {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
template: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function loadStockAudits(): StockAudit[];
|
package/dist/audits.js
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// lib/audits/index.ts
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
|
|
4
|
+
// lib/markdown/markdown-utilities.ts
|
|
5
|
+
function extractTitle(content) {
|
|
6
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
7
|
+
return match ? match[1].trim() : null;
|
|
8
|
+
}
|
|
9
|
+
function extractOpeningSentence(content) {
|
|
10
|
+
const lines = content.split(`
|
|
11
|
+
`);
|
|
12
|
+
let h1Index = -1;
|
|
13
|
+
for (let i = 0;i < lines.length; i++) {
|
|
14
|
+
if (lines[i].match(/^#\s+.+$/)) {
|
|
15
|
+
h1Index = i;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (h1Index === -1) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
let paragraphStart = -1;
|
|
23
|
+
for (let i = h1Index + 1;i < lines.length; i++) {
|
|
24
|
+
const line = lines[i].trim();
|
|
25
|
+
if (line !== "") {
|
|
26
|
+
paragraphStart = i;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (paragraphStart === -1) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const firstLine = lines[paragraphStart];
|
|
34
|
+
const trimmedFirstLine = firstLine.trim();
|
|
35
|
+
if (trimmedFirstLine.startsWith("#") || trimmedFirstLine.startsWith("-") || trimmedFirstLine.startsWith("*") || trimmedFirstLine.startsWith("+") || trimmedFirstLine.match(/^\d+\./) || trimmedFirstLine.startsWith("```") || trimmedFirstLine.startsWith(">")) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
let paragraph = "";
|
|
39
|
+
for (let i = paragraphStart;i < lines.length; i++) {
|
|
40
|
+
const line = lines[i].trim();
|
|
41
|
+
if (line === "")
|
|
42
|
+
break;
|
|
43
|
+
if (line.startsWith("#") || line.startsWith("```") || line.startsWith(">")) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
paragraph += (paragraph ? " " : "") + line;
|
|
47
|
+
}
|
|
48
|
+
const sentenceMatch = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
49
|
+
if (!sentenceMatch) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return sentenceMatch[1];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// lib/cli/dedent.ts
|
|
56
|
+
function dedent(strings, ...values) {
|
|
57
|
+
const result = strings.reduce((acc, part, index) => acc + part + (values[index] ?? ""), "");
|
|
58
|
+
const lines = result.split(`
|
|
59
|
+
`);
|
|
60
|
+
const indent = lines.filter((line) => line.trim()).reduce((min, line) => Math.min(min, line.match(/^\s*/)[0].length), Number.POSITIVE_INFINITY);
|
|
61
|
+
return lines.map((line) => line.slice(indent)).join(`
|
|
62
|
+
`).trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// lib/audits/stock-audits.ts
|
|
66
|
+
function agentDeveloperExperience() {
|
|
67
|
+
return dedent`
|
|
68
|
+
# Agent Developer Experience
|
|
69
|
+
|
|
70
|
+
Review the codebase to ensure agents have everything they need to operate effectively. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
71
|
+
|
|
72
|
+
## Scope
|
|
73
|
+
|
|
74
|
+
Focus on these areas:
|
|
75
|
+
|
|
76
|
+
1. **Context window efficiency** - Are files small and well-organized?
|
|
77
|
+
2. **Test coverage** - Can agents verify correctness through tests?
|
|
78
|
+
3. **Feedback loop speed** - How fast are checks and tests?
|
|
79
|
+
4. **Debugging tools** - Can agents diagnose issues without trial and error?
|
|
80
|
+
5. **Structured logging** - Is system behavior observable through logs?
|
|
81
|
+
|
|
82
|
+
## Principles
|
|
83
|
+
|
|
84
|
+
(none)
|
|
85
|
+
|
|
86
|
+
## Blocked By
|
|
87
|
+
|
|
88
|
+
(none)
|
|
89
|
+
|
|
90
|
+
## Definition of Done
|
|
91
|
+
|
|
92
|
+
- [ ] Reviewed file sizes and organization for context window fit
|
|
93
|
+
- [ ] Verified test coverage is sufficient for agent verification
|
|
94
|
+
- [ ] Measured feedback loop speed (time from change to check result)
|
|
95
|
+
- [ ] Confirmed debugging tools and structured logging are in place
|
|
96
|
+
- [ ] Proposed ideas for any improvements identified
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
function deadCode() {
|
|
100
|
+
return dedent`
|
|
101
|
+
# Dead Code
|
|
102
|
+
|
|
103
|
+
Find and remove unused code to improve maintainability and reduce bundle size. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
104
|
+
|
|
105
|
+
## Scope
|
|
106
|
+
|
|
107
|
+
Focus on these areas:
|
|
108
|
+
|
|
109
|
+
1. **Unused exports** - Functions, classes, constants that are never imported
|
|
110
|
+
2. **Unreachable code** - Code after return statements, impossible conditions
|
|
111
|
+
3. **Orphaned files** - Files that are not imported anywhere
|
|
112
|
+
4. **Unused dependencies** - Packages in package.json not used in code
|
|
113
|
+
5. **Commented-out code** - Old code left in comments
|
|
114
|
+
|
|
115
|
+
## Principles
|
|
116
|
+
|
|
117
|
+
(none)
|
|
118
|
+
|
|
119
|
+
## Blocked By
|
|
120
|
+
|
|
121
|
+
(none)
|
|
122
|
+
|
|
123
|
+
## Definition of Done
|
|
124
|
+
|
|
125
|
+
- [ ] Ran static analysis tools to find unused exports
|
|
126
|
+
- [ ] Identified files with no incoming imports
|
|
127
|
+
- [ ] Listed unused dependencies
|
|
128
|
+
- [ ] Reviewed commented-out code blocks
|
|
129
|
+
- [ ] Created list of code safe to remove
|
|
130
|
+
- [ ] Verified removal won't break dynamic imports or reflection
|
|
131
|
+
- [ ] Proposed ideas for any dead code worth removing
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
function factsVerification() {
|
|
135
|
+
return dedent`
|
|
136
|
+
# Facts Verification
|
|
137
|
+
|
|
138
|
+
Review \`.dust/facts/\` to ensure documented facts match current reality. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
139
|
+
|
|
140
|
+
## Scope
|
|
141
|
+
|
|
142
|
+
Focus on these areas:
|
|
143
|
+
|
|
144
|
+
1. **Accuracy** - Do documented facts reflect the current codebase?
|
|
145
|
+
2. **Completeness** - Are important implementation details documented?
|
|
146
|
+
3. **Staleness** - Have facts become outdated due to recent changes?
|
|
147
|
+
4. **Relevance** - Are all facts still useful for understanding the project?
|
|
148
|
+
|
|
149
|
+
## Principles
|
|
150
|
+
|
|
151
|
+
(none)
|
|
152
|
+
|
|
153
|
+
## Blocked By
|
|
154
|
+
|
|
155
|
+
(none)
|
|
156
|
+
|
|
157
|
+
## Definition of Done
|
|
158
|
+
|
|
159
|
+
- [ ] Read each fact file in \`.dust/facts/\`
|
|
160
|
+
- [ ] Verified each fact against current codebase
|
|
161
|
+
- [ ] Identified outdated or inaccurate facts
|
|
162
|
+
- [ ] Listed missing facts that would help agents
|
|
163
|
+
- [ ] Updated or removed stale facts
|
|
164
|
+
- [ ] Proposed ideas for any facts improvements needed
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
function ideasFromCommits() {
|
|
168
|
+
return dedent`
|
|
169
|
+
# Ideas from Commits
|
|
170
|
+
|
|
171
|
+
Review recent commit history to identify follow-up improvement ideas. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues or opportunities you identify, avoiding duplication.
|
|
172
|
+
|
|
173
|
+
## Scope
|
|
174
|
+
|
|
175
|
+
Focus on these areas:
|
|
176
|
+
|
|
177
|
+
1. **Technical debt** - Did recent work introduce shortcuts?
|
|
178
|
+
2. **Incomplete work** - Are there TODO comments or partial implementations?
|
|
179
|
+
3. **Pattern opportunities** - Can recent changes be generalized?
|
|
180
|
+
4. **Test gaps** - Do recent changes have adequate test coverage?
|
|
181
|
+
|
|
182
|
+
## Principles
|
|
183
|
+
|
|
184
|
+
(none)
|
|
185
|
+
|
|
186
|
+
## Blocked By
|
|
187
|
+
|
|
188
|
+
(none)
|
|
189
|
+
|
|
190
|
+
## Definition of Done
|
|
191
|
+
|
|
192
|
+
- [ ] Reviewed commits from the last 20 commits
|
|
193
|
+
- [ ] Identified patterns or shortcuts worth addressing
|
|
194
|
+
- [ ] Listed TODO comments added in recent commits
|
|
195
|
+
- [ ] Noted areas where changes could be generalized
|
|
196
|
+
- [ ] Proposed follow-up ideas for any issues identified
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
function ideasFromPrinciples() {
|
|
200
|
+
return dedent`
|
|
201
|
+
# Ideas from Principles
|
|
202
|
+
|
|
203
|
+
Review \`.dust/principles/\` to generate new improvement ideas. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues or opportunities you identify, avoiding duplication.
|
|
204
|
+
|
|
205
|
+
## Scope
|
|
206
|
+
|
|
207
|
+
Focus on these areas:
|
|
208
|
+
|
|
209
|
+
1. **Unmet principles** - Which principles lack supporting work?
|
|
210
|
+
2. **Gap analysis** - Where does the codebase fall short of principles?
|
|
211
|
+
3. **New opportunities** - What work would better achieve each principle?
|
|
212
|
+
4. **Principle alignment** - Are current tasks aligned with stated principles?
|
|
213
|
+
|
|
214
|
+
## Principles
|
|
215
|
+
|
|
216
|
+
(none)
|
|
217
|
+
|
|
218
|
+
## Blocked By
|
|
219
|
+
|
|
220
|
+
(none)
|
|
221
|
+
|
|
222
|
+
## Definition of Done
|
|
223
|
+
|
|
224
|
+
- [ ] Read each principle file in \`.dust/principles/\`
|
|
225
|
+
- [ ] Analyzed codebase for alignment with each principle
|
|
226
|
+
- [ ] Listed gaps between current state and principle intent
|
|
227
|
+
- [ ] Proposed new ideas for unmet or underserved principles
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
function performanceReview() {
|
|
231
|
+
return dedent`
|
|
232
|
+
# Performance Review
|
|
233
|
+
|
|
234
|
+
Review the application for performance issues and optimization opportunities. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
235
|
+
|
|
236
|
+
## Scope
|
|
237
|
+
|
|
238
|
+
Focus on these areas:
|
|
239
|
+
|
|
240
|
+
1. **Startup time** - How fast does the application start?
|
|
241
|
+
2. **Command latency** - How responsive are CLI commands?
|
|
242
|
+
3. **Memory usage** - Is memory being used efficiently?
|
|
243
|
+
4. **Build performance** - How fast is the build process?
|
|
244
|
+
5. **Test speed** - Are tests running efficiently?
|
|
245
|
+
|
|
246
|
+
## Principles
|
|
247
|
+
|
|
248
|
+
(none)
|
|
249
|
+
|
|
250
|
+
## Blocked By
|
|
251
|
+
|
|
252
|
+
(none)
|
|
253
|
+
|
|
254
|
+
## Definition of Done
|
|
255
|
+
|
|
256
|
+
- [ ] Measured startup time for common commands
|
|
257
|
+
- [ ] Profiled memory usage during typical operations
|
|
258
|
+
- [ ] Identified slow commands or operations
|
|
259
|
+
- [ ] Listed optimization opportunities by impact
|
|
260
|
+
- [ ] Proposed ideas for any performance improvements identified
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
function securityReview() {
|
|
264
|
+
return dedent`
|
|
265
|
+
# Security Review
|
|
266
|
+
|
|
267
|
+
Review the codebase for common security vulnerabilities and misconfigurations. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
268
|
+
|
|
269
|
+
## Scope
|
|
270
|
+
|
|
271
|
+
Focus on these areas:
|
|
272
|
+
|
|
273
|
+
1. **Hardcoded secrets** - API keys, passwords, tokens in source code
|
|
274
|
+
2. **Injection vulnerabilities** - SQL injection, command injection, XSS
|
|
275
|
+
3. **Authentication issues** - Weak password handling, missing auth checks
|
|
276
|
+
4. **Sensitive data exposure** - Logging sensitive data, insecure storage
|
|
277
|
+
5. **Dependency vulnerabilities** - Known CVEs in dependencies
|
|
278
|
+
|
|
279
|
+
## Principles
|
|
280
|
+
|
|
281
|
+
(none)
|
|
282
|
+
|
|
283
|
+
## Blocked By
|
|
284
|
+
|
|
285
|
+
(none)
|
|
286
|
+
|
|
287
|
+
## Definition of Done
|
|
288
|
+
|
|
289
|
+
- [ ] Searched for hardcoded secrets (API keys, passwords, tokens)
|
|
290
|
+
- [ ] Reviewed input validation and sanitization
|
|
291
|
+
- [ ] Checked authentication and authorization logic
|
|
292
|
+
- [ ] Verified sensitive data is not logged or exposed
|
|
293
|
+
- [ ] Ran dependency audit for known vulnerabilities
|
|
294
|
+
- [ ] Documented any findings with severity ratings
|
|
295
|
+
- [ ] Proposed ideas for any security issues found
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
function staleIdeas() {
|
|
299
|
+
return dedent`
|
|
300
|
+
# Stale Ideas
|
|
301
|
+
|
|
302
|
+
Review \`.dust/ideas/\` to identify ideas that have become stale or irrelevant. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
303
|
+
|
|
304
|
+
## Scope
|
|
305
|
+
|
|
306
|
+
Focus on these areas:
|
|
307
|
+
|
|
308
|
+
1. **Age** - Ideas unchanged for many commits may need attention
|
|
309
|
+
2. **Relevance** - Has the project evolved past the idea?
|
|
310
|
+
3. **Actionability** - Can the idea be converted to a task?
|
|
311
|
+
4. **Duplication** - Are there overlapping or redundant ideas?
|
|
312
|
+
|
|
313
|
+
## Principles
|
|
314
|
+
|
|
315
|
+
(none)
|
|
316
|
+
|
|
317
|
+
## Blocked By
|
|
318
|
+
|
|
319
|
+
(none)
|
|
320
|
+
|
|
321
|
+
## Definition of Done
|
|
322
|
+
|
|
323
|
+
- [ ] Listed all ideas with their last modification date
|
|
324
|
+
- [ ] Identified ideas unchanged for 50+ commits
|
|
325
|
+
- [ ] Reviewed each stale idea for current relevance
|
|
326
|
+
- [ ] Promoted actionable ideas to tasks
|
|
327
|
+
- [ ] Deleted ideas that are no longer relevant
|
|
328
|
+
`;
|
|
329
|
+
}
|
|
330
|
+
function testCoverage() {
|
|
331
|
+
return dedent`
|
|
332
|
+
# Test Coverage
|
|
333
|
+
|
|
334
|
+
Identify untested code paths and areas that need additional test coverage. Review existing ideas in \`./.ideas/\` and the recent history of \`./.dust/ideas\` to understand what has been proposed or considered historically, then create new idea files in \`./.ideas/\` for any issues you identify, avoiding duplication.
|
|
335
|
+
|
|
336
|
+
## Scope
|
|
337
|
+
|
|
338
|
+
Focus on these areas:
|
|
339
|
+
|
|
340
|
+
1. **Core business logic** - Functions that handle critical operations
|
|
341
|
+
2. **Edge cases** - Boundary conditions, error handling paths
|
|
342
|
+
3. **Integration points** - API endpoints, database operations
|
|
343
|
+
4. **User-facing features** - UI components, form validation
|
|
344
|
+
5. **Recent changes** - Code modified in the last few commits
|
|
345
|
+
|
|
346
|
+
## Principles
|
|
347
|
+
|
|
348
|
+
(none)
|
|
349
|
+
|
|
350
|
+
## Blocked By
|
|
351
|
+
|
|
352
|
+
(none)
|
|
353
|
+
|
|
354
|
+
## Definition of Done
|
|
355
|
+
|
|
356
|
+
- [ ] Identified modules with low or no test coverage
|
|
357
|
+
- [ ] Listed critical paths that lack tests
|
|
358
|
+
- [ ] Prioritized areas by risk and importance
|
|
359
|
+
- [ ] Proposed ideas for any test coverage gaps identified
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
var stockAuditFunctions = {
|
|
363
|
+
"agent-developer-experience": agentDeveloperExperience,
|
|
364
|
+
"dead-code": deadCode,
|
|
365
|
+
"facts-verification": factsVerification,
|
|
366
|
+
"ideas-from-commits": ideasFromCommits,
|
|
367
|
+
"ideas-from-principles": ideasFromPrinciples,
|
|
368
|
+
"performance-review": performanceReview,
|
|
369
|
+
"security-review": securityReview,
|
|
370
|
+
"stale-ideas": staleIdeas,
|
|
371
|
+
"test-coverage": testCoverage
|
|
372
|
+
};
|
|
373
|
+
function loadStockAudits() {
|
|
374
|
+
return Object.entries(stockAuditFunctions).sort(([a], [b]) => a.localeCompare(b)).map(([name, render]) => {
|
|
375
|
+
const template = render();
|
|
376
|
+
const description = extractOpeningSentence(template);
|
|
377
|
+
return { name, description, template };
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// lib/audits/index.ts
|
|
382
|
+
function transformAuditContent(content) {
|
|
383
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
384
|
+
if (!titleMatch) {
|
|
385
|
+
return content;
|
|
386
|
+
}
|
|
387
|
+
const originalTitle = titleMatch[1];
|
|
388
|
+
return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
|
|
389
|
+
}
|
|
390
|
+
function buildAuditsRepository(fileSystem, dustPath) {
|
|
391
|
+
const userAuditsPath = `${dustPath}/config/audits`;
|
|
392
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
393
|
+
async function loadAllAudits() {
|
|
394
|
+
const audits = new Map;
|
|
395
|
+
for (const stockAudit of loadStockAudits()) {
|
|
396
|
+
audits.set(stockAudit.name, {
|
|
397
|
+
name: stockAudit.name,
|
|
398
|
+
title: extractTitle(stockAudit.template),
|
|
399
|
+
description: stockAudit.description,
|
|
400
|
+
template: stockAudit.template,
|
|
401
|
+
source: "stock"
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
if (fileSystem.exists(userAuditsPath)) {
|
|
405
|
+
const files = await fileSystem.readdir(userAuditsPath);
|
|
406
|
+
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
407
|
+
for (const file of mdFiles) {
|
|
408
|
+
const name = basename(file, ".md");
|
|
409
|
+
const filePath = `${userAuditsPath}/${file}`;
|
|
410
|
+
const content = await fileSystem.readFile(filePath);
|
|
411
|
+
const title = extractTitle(content) || name;
|
|
412
|
+
const description = extractOpeningSentence(content) || "";
|
|
413
|
+
const relativePath = `.dust/config/audits/${file}`;
|
|
414
|
+
audits.set(name, {
|
|
415
|
+
name,
|
|
416
|
+
title,
|
|
417
|
+
description,
|
|
418
|
+
template: content,
|
|
419
|
+
source: relativePath
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return audits;
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
async listAudits() {
|
|
427
|
+
const auditsMap = await loadAllAudits();
|
|
428
|
+
return Array.from(auditsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
429
|
+
},
|
|
430
|
+
async parseAudit(options) {
|
|
431
|
+
const auditsMap = await loadAllAudits();
|
|
432
|
+
const audit = auditsMap.get(options.name);
|
|
433
|
+
if (!audit) {
|
|
434
|
+
throw new Error(`Audit not found: "${options.name}"`);
|
|
435
|
+
}
|
|
436
|
+
return audit;
|
|
437
|
+
},
|
|
438
|
+
async createAuditTask(options) {
|
|
439
|
+
const audit = await this.parseAudit(options);
|
|
440
|
+
const taskFilePath = `${tasksPath}/audit-${options.name}.md`;
|
|
441
|
+
const relativeTaskPath = `.dust/tasks/audit-${options.name}.md`;
|
|
442
|
+
if (fileSystem.exists(taskFilePath)) {
|
|
443
|
+
throw new Error(`Audit task already exists at ${relativeTaskPath}`);
|
|
444
|
+
}
|
|
445
|
+
const transformedContent = transformAuditContent(audit.template);
|
|
446
|
+
await fileSystem.mkdir(tasksPath, { recursive: true });
|
|
447
|
+
await fileSystem.writeFile(taskFilePath, transformedContent);
|
|
448
|
+
return {
|
|
449
|
+
filePath: taskFilePath,
|
|
450
|
+
relativePath: relativeTaskPath
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
export {
|
|
456
|
+
transformAuditContent,
|
|
457
|
+
loadStockAudits,
|
|
458
|
+
buildAuditsRepository
|
|
459
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedent tagged template literal helper
|
|
3
|
+
*
|
|
4
|
+
* Strips common leading whitespace from multi-line template literals,
|
|
5
|
+
* making it possible to write properly indented code while producing
|
|
6
|
+
* clean output.
|
|
7
|
+
*/
|
|
8
|
+
export declare function dedent(strings: TemplateStringsArray, ...values: unknown[]): string;
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Common types for CLI commands
|
|
3
3
|
*/
|
|
4
|
+
import type { FileSystem, GlobScanner } from '../filesystem/types';
|
|
5
|
+
export type { FileSystem, GlobScanner, ReadableFileSystem, WriteOptions, } from '../filesystem/types';
|
|
4
6
|
export interface CommandContext {
|
|
5
7
|
cwd: string;
|
|
6
8
|
stdout: (message: string) => void;
|
|
@@ -10,27 +12,6 @@ export interface CommandContext {
|
|
|
10
12
|
export interface CommandResult {
|
|
11
13
|
exitCode: number;
|
|
12
14
|
}
|
|
13
|
-
export interface WriteOptions {
|
|
14
|
-
flag?: 'w' | 'wx';
|
|
15
|
-
}
|
|
16
|
-
export interface ReadableFileSystem {
|
|
17
|
-
exists: (path: string) => boolean;
|
|
18
|
-
readFile: (path: string) => Promise<string>;
|
|
19
|
-
readdir: (path: string) => Promise<string[]>;
|
|
20
|
-
isDirectory: (path: string) => boolean;
|
|
21
|
-
}
|
|
22
|
-
export interface FileSystem extends ReadableFileSystem {
|
|
23
|
-
writeFile: (path: string, content: string, options?: WriteOptions) => Promise<void>;
|
|
24
|
-
mkdir: (path: string, options?: {
|
|
25
|
-
recursive?: boolean;
|
|
26
|
-
}) => Promise<void>;
|
|
27
|
-
chmod: (path: string, mode: number) => Promise<void>;
|
|
28
|
-
getFileCreationTime: (path: string) => number;
|
|
29
|
-
rename: (oldPath: string, newPath: string) => Promise<void>;
|
|
30
|
-
}
|
|
31
|
-
export interface GlobScanner {
|
|
32
|
-
scan: (dir: string) => AsyncIterable<string>;
|
|
33
|
-
}
|
|
34
15
|
export interface CheckConfig {
|
|
35
16
|
name: string;
|
|
36
17
|
command: string;
|
package/dist/dust.js
CHANGED
|
@@ -274,6 +274,9 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
// lib/version.ts
|
|
278
|
+
var DUST_VERSION = "0.1.63";
|
|
279
|
+
|
|
277
280
|
// lib/cli/dedent.ts
|
|
278
281
|
function dedent(strings, ...values) {
|
|
279
282
|
const result = strings.reduce((acc, part, index) => acc + part + (values[index] ?? ""), "");
|
|
@@ -902,6 +905,16 @@ function loadStockAudits() {
|
|
|
902
905
|
});
|
|
903
906
|
}
|
|
904
907
|
|
|
908
|
+
// lib/audits/index.ts
|
|
909
|
+
function transformAuditContent(content) {
|
|
910
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
911
|
+
if (!titleMatch) {
|
|
912
|
+
return content;
|
|
913
|
+
}
|
|
914
|
+
const originalTitle = titleMatch[1];
|
|
915
|
+
return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
|
|
916
|
+
}
|
|
917
|
+
|
|
905
918
|
// lib/cli/colors.ts
|
|
906
919
|
var ANSI_COLORS = {
|
|
907
920
|
reset: "\x1B[0m",
|
|
@@ -936,14 +949,6 @@ function getColors() {
|
|
|
936
949
|
}
|
|
937
950
|
|
|
938
951
|
// lib/cli/commands/audit.ts
|
|
939
|
-
function transformAuditContent(content) {
|
|
940
|
-
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
941
|
-
if (!titleMatch) {
|
|
942
|
-
return content;
|
|
943
|
-
}
|
|
944
|
-
const originalTitle = titleMatch[1];
|
|
945
|
-
return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
|
|
946
|
-
}
|
|
947
952
|
async function addAudit(auditName, dependencies) {
|
|
948
953
|
const { context, fileSystem, settings } = dependencies;
|
|
949
954
|
const dustPath = `${context.cwd}/.dust`;
|
|
@@ -1034,7 +1039,6 @@ import { accessSync, statSync } from "node:fs";
|
|
|
1034
1039
|
import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
1035
1040
|
import { createServer as httpCreateServer } from "node:http";
|
|
1036
1041
|
import { homedir } from "node:os";
|
|
1037
|
-
import { join as join9 } from "node:path";
|
|
1038
1042
|
|
|
1039
1043
|
// lib/bucket/auth.ts
|
|
1040
1044
|
import { join as join4 } from "node:path";
|
|
@@ -1202,8 +1206,14 @@ function getLogLines(buffer) {
|
|
|
1202
1206
|
return buffer.lines;
|
|
1203
1207
|
}
|
|
1204
1208
|
|
|
1209
|
+
// lib/bucket/paths.ts
|
|
1210
|
+
import { join as join5 } from "node:path";
|
|
1211
|
+
function getReposDir(env, homeDir) {
|
|
1212
|
+
return env.DUST_REPOS_DIR || join5(homeDir, ".dust", "repos");
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1205
1215
|
// lib/bucket/repository.ts
|
|
1206
|
-
import { dirname as
|
|
1216
|
+
import { dirname as dirname2 } from "node:path";
|
|
1207
1217
|
|
|
1208
1218
|
// lib/claude/spawn-claude-code.ts
|
|
1209
1219
|
import { spawn as nodeSpawn } from "node:child_process";
|
|
@@ -1622,7 +1632,7 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
1622
1632
|
}
|
|
1623
1633
|
|
|
1624
1634
|
// lib/logging/index.ts
|
|
1625
|
-
import { join as
|
|
1635
|
+
import { join as join6 } from "node:path";
|
|
1626
1636
|
|
|
1627
1637
|
// lib/logging/match.ts
|
|
1628
1638
|
function parsePatterns(debug) {
|
|
@@ -1706,8 +1716,8 @@ function createLoggingService() {
|
|
|
1706
1716
|
return {
|
|
1707
1717
|
enableFileLogs(scope, sinkForTesting) {
|
|
1708
1718
|
const existing = process.env[DUST_LOG_FILE];
|
|
1709
|
-
const logDir = process.env.DUST_LOG_DIR ??
|
|
1710
|
-
const path = existing ??
|
|
1719
|
+
const logDir = process.env.DUST_LOG_DIR ?? join6(process.cwd(), "log");
|
|
1720
|
+
const path = existing ?? join6(logDir, `${scope}.log`);
|
|
1711
1721
|
if (!existing) {
|
|
1712
1722
|
process.env[DUST_LOG_FILE] = path;
|
|
1713
1723
|
}
|
|
@@ -1747,10 +1757,10 @@ var createLogger = defaultService.createLogger.bind(defaultService);
|
|
|
1747
1757
|
var isEnabled = defaultService.isEnabled.bind(defaultService);
|
|
1748
1758
|
|
|
1749
1759
|
// lib/bucket/repository-git.ts
|
|
1750
|
-
import { join as
|
|
1760
|
+
import { join as join7 } from "node:path";
|
|
1751
1761
|
function getRepoPath(repoName, reposDir) {
|
|
1752
1762
|
const safeName = repoName.replace(/[^a-zA-Z0-9-_/]/g, "-");
|
|
1753
|
-
return
|
|
1763
|
+
return join7(reposDir, safeName);
|
|
1754
1764
|
}
|
|
1755
1765
|
async function cloneRepository(repository, targetPath, spawn, context) {
|
|
1756
1766
|
return new Promise((resolve) => {
|
|
@@ -1818,10 +1828,7 @@ function formatAgentEvent(event) {
|
|
|
1818
1828
|
|
|
1819
1829
|
// lib/cli/commands/loop.ts
|
|
1820
1830
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1821
|
-
import { readFileSync } from "node:fs";
|
|
1822
1831
|
import os from "node:os";
|
|
1823
|
-
import { dirname as dirname2, join as join7 } from "node:path";
|
|
1824
|
-
import { fileURLToPath } from "node:url";
|
|
1825
1832
|
|
|
1826
1833
|
// lib/artifacts/workflow-tasks.ts
|
|
1827
1834
|
var IDEA_TRANSITION_PREFIXES = [
|
|
@@ -1970,26 +1977,12 @@ async function next(dependencies) {
|
|
|
1970
1977
|
}
|
|
1971
1978
|
|
|
1972
1979
|
// lib/cli/commands/loop.ts
|
|
1973
|
-
var __dirname2 = dirname2(fileURLToPath(import.meta.url));
|
|
1974
|
-
function getDustVersion() {
|
|
1975
|
-
const candidates = [
|
|
1976
|
-
join7(__dirname2, "../../../package.json"),
|
|
1977
|
-
join7(__dirname2, "../package.json")
|
|
1978
|
-
];
|
|
1979
|
-
for (const candidate of candidates) {
|
|
1980
|
-
try {
|
|
1981
|
-
const packageJson = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
1982
|
-
return packageJson.version ?? "unknown";
|
|
1983
|
-
} catch {}
|
|
1984
|
-
}
|
|
1985
|
-
return "unknown";
|
|
1986
|
-
}
|
|
1987
1980
|
function getEnvironmentContext(cwd) {
|
|
1988
1981
|
return {
|
|
1989
1982
|
machineName: os.hostname(),
|
|
1990
1983
|
cwd,
|
|
1991
1984
|
platform: `${os.platform()} ${os.release()}`,
|
|
1992
|
-
dustVersion:
|
|
1985
|
+
dustVersion: DUST_VERSION,
|
|
1993
1986
|
runtimeVersion: process.version
|
|
1994
1987
|
};
|
|
1995
1988
|
}
|
|
@@ -2098,7 +2091,7 @@ async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAg
|
|
|
2098
2091
|
const { context } = dependencies;
|
|
2099
2092
|
const { spawn, run: run2 } = loopDependencies;
|
|
2100
2093
|
const agentName = loopDependencies.agentType === "codex" ? "Codex" : "Claude";
|
|
2101
|
-
const { onRawEvent, hooksInstalled = false, signal } = options;
|
|
2094
|
+
const { onRawEvent, hooksInstalled = false, signal, logger = log } = options;
|
|
2102
2095
|
log("syncing with remote");
|
|
2103
2096
|
onLoopEvent({ type: "loop.syncing" });
|
|
2104
2097
|
const pullResult = await gitPull(context.cwd, spawn);
|
|
@@ -2199,7 +2192,7 @@ ${instructions}`;
|
|
|
2199
2192
|
return "ran_claude";
|
|
2200
2193
|
} catch (error) {
|
|
2201
2194
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2202
|
-
|
|
2195
|
+
logger(`${agentName} error on task ${task.title ?? task.path}: ${errorMessage}`);
|
|
2203
2196
|
context.stderr(`${agentName} exited with error: ${errorMessage}`);
|
|
2204
2197
|
onAgentEvent?.({
|
|
2205
2198
|
type: "agent-session-ended",
|
|
@@ -2475,7 +2468,7 @@ async function addRepository(repository, manager, repoDeps, context) {
|
|
|
2475
2468
|
}
|
|
2476
2469
|
log3(`adding repository ${repository.name}`);
|
|
2477
2470
|
const repoPath = getRepoPath(repository.name, repoDeps.getReposDir());
|
|
2478
|
-
await repoDeps.fileSystem.mkdir(
|
|
2471
|
+
await repoDeps.fileSystem.mkdir(dirname2(repoPath), { recursive: true });
|
|
2479
2472
|
if (repoDeps.fileSystem.exists(repoPath)) {
|
|
2480
2473
|
await removeRepository(repoPath, repoDeps.spawn, context);
|
|
2481
2474
|
}
|
|
@@ -3056,11 +3049,11 @@ function defaultOpenBrowser(url) {
|
|
|
3056
3049
|
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
3057
3050
|
nodeSpawn3(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
3058
3051
|
}
|
|
3059
|
-
function
|
|
3060
|
-
|
|
3052
|
+
function createAuthFileSystem(dependencies) {
|
|
3053
|
+
return {
|
|
3061
3054
|
exists: (path) => {
|
|
3062
3055
|
try {
|
|
3063
|
-
accessSync(path);
|
|
3056
|
+
dependencies.accessSync(path);
|
|
3064
3057
|
return true;
|
|
3065
3058
|
} catch {
|
|
3066
3059
|
return false;
|
|
@@ -3068,19 +3061,31 @@ function createDefaultBucketDependencies() {
|
|
|
3068
3061
|
},
|
|
3069
3062
|
isDirectory: (path) => {
|
|
3070
3063
|
try {
|
|
3071
|
-
return statSync(path).isDirectory();
|
|
3064
|
+
return dependencies.statSync(path).isDirectory();
|
|
3072
3065
|
} catch {
|
|
3073
3066
|
return false;
|
|
3074
3067
|
}
|
|
3075
3068
|
},
|
|
3076
|
-
getFileCreationTime: (path) => statSync(path).birthtimeMs,
|
|
3077
|
-
readFile: (path) => readFile(path, "utf8"),
|
|
3078
|
-
writeFile: (path, content) => writeFile(path, content, "utf8"),
|
|
3079
|
-
mkdir: (path, options) => mkdir(path, options).then(() => {}),
|
|
3080
|
-
readdir: (path) => readdir(path),
|
|
3081
|
-
chmod: (path, mode) => chmod(path, mode),
|
|
3082
|
-
rename: (oldPath, newPath) =>
|
|
3069
|
+
getFileCreationTime: (path) => dependencies.statSync(path).birthtimeMs,
|
|
3070
|
+
readFile: (path) => dependencies.readFile(path, "utf8"),
|
|
3071
|
+
writeFile: (path, content) => dependencies.writeFile(path, content, "utf8"),
|
|
3072
|
+
mkdir: (path, options) => dependencies.mkdir(path, options).then(() => {}),
|
|
3073
|
+
readdir: (path) => dependencies.readdir(path),
|
|
3074
|
+
chmod: (path, mode) => dependencies.chmod(path, mode),
|
|
3075
|
+
rename: (oldPath, newPath) => dependencies.rename(oldPath, newPath)
|
|
3083
3076
|
};
|
|
3077
|
+
}
|
|
3078
|
+
function createDefaultBucketDependencies() {
|
|
3079
|
+
const authFileSystem = createAuthFileSystem({
|
|
3080
|
+
accessSync,
|
|
3081
|
+
statSync,
|
|
3082
|
+
readFile,
|
|
3083
|
+
writeFile,
|
|
3084
|
+
mkdir,
|
|
3085
|
+
readdir,
|
|
3086
|
+
chmod,
|
|
3087
|
+
rename: (oldPath, newPath) => import("node:fs/promises").then((mod) => mod.rename(oldPath, newPath))
|
|
3088
|
+
});
|
|
3084
3089
|
return {
|
|
3085
3090
|
spawn: nodeSpawn3,
|
|
3086
3091
|
createWebSocket: defaultCreateWebSocket,
|
|
@@ -3091,7 +3096,7 @@ function createDefaultBucketDependencies() {
|
|
|
3091
3096
|
writeStdout: defaultWriteStdout,
|
|
3092
3097
|
isTTY: process.stdout.isTTY ?? false,
|
|
3093
3098
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
3094
|
-
getReposDir: () => process.env
|
|
3099
|
+
getReposDir: () => getReposDir(process.env, homedir()),
|
|
3095
3100
|
auth: {
|
|
3096
3101
|
createServer: defaultCreateServer,
|
|
3097
3102
|
openBrowser: defaultOpenBrowser,
|
|
@@ -3517,7 +3522,7 @@ function runBufferedProcess(spawnFn, command, commandArguments, cwd, shell, time
|
|
|
3517
3522
|
}
|
|
3518
3523
|
|
|
3519
3524
|
// lib/cli/commands/lint-markdown.ts
|
|
3520
|
-
import { join as
|
|
3525
|
+
import { join as join8 } from "node:path";
|
|
3521
3526
|
|
|
3522
3527
|
// lib/lint/validators/content-validator.ts
|
|
3523
3528
|
var REQUIRED_HEADINGS = [
|
|
@@ -3807,7 +3812,7 @@ function validateIdeaTransitionTitle(filePath, content, ideasPath, fileSystem) {
|
|
|
3807
3812
|
}
|
|
3808
3813
|
|
|
3809
3814
|
// lib/lint/validators/link-validator.ts
|
|
3810
|
-
import { dirname as
|
|
3815
|
+
import { dirname as dirname3, resolve } from "node:path";
|
|
3811
3816
|
var SEMANTIC_RULES = [
|
|
3812
3817
|
{
|
|
3813
3818
|
section: "## Principles",
|
|
@@ -3824,7 +3829,7 @@ function validateLinks(filePath, content, fileSystem) {
|
|
|
3824
3829
|
const violations = [];
|
|
3825
3830
|
const lines = content.split(`
|
|
3826
3831
|
`);
|
|
3827
|
-
const fileDir =
|
|
3832
|
+
const fileDir = dirname3(filePath);
|
|
3828
3833
|
for (let i = 0;i < lines.length; i++) {
|
|
3829
3834
|
const line = lines[i];
|
|
3830
3835
|
const linkPattern = new RegExp(MARKDOWN_LINK_PATTERN.source, "g");
|
|
@@ -3851,7 +3856,7 @@ function validateSemanticLinks(filePath, content) {
|
|
|
3851
3856
|
const violations = [];
|
|
3852
3857
|
const lines = content.split(`
|
|
3853
3858
|
`);
|
|
3854
|
-
const fileDir =
|
|
3859
|
+
const fileDir = dirname3(filePath);
|
|
3855
3860
|
let currentSection = null;
|
|
3856
3861
|
for (let i = 0;i < lines.length; i++) {
|
|
3857
3862
|
const line = lines[i];
|
|
@@ -3902,7 +3907,7 @@ function validatePrincipleHierarchyLinks(filePath, content) {
|
|
|
3902
3907
|
const violations = [];
|
|
3903
3908
|
const lines = content.split(`
|
|
3904
3909
|
`);
|
|
3905
|
-
const fileDir =
|
|
3910
|
+
const fileDir = dirname3(filePath);
|
|
3906
3911
|
let currentSection = null;
|
|
3907
3912
|
for (let i = 0;i < lines.length; i++) {
|
|
3908
3913
|
const line = lines[i];
|
|
@@ -3951,7 +3956,7 @@ function validatePrincipleHierarchyLinks(filePath, content) {
|
|
|
3951
3956
|
}
|
|
3952
3957
|
|
|
3953
3958
|
// lib/lint/validators/principle-hierarchy.ts
|
|
3954
|
-
import { dirname as
|
|
3959
|
+
import { dirname as dirname4, resolve as resolve2 } from "node:path";
|
|
3955
3960
|
var REQUIRED_PRINCIPLE_HEADINGS = ["## Parent Principle", "## Sub-Principles"];
|
|
3956
3961
|
function validatePrincipleHierarchySections(filePath, content) {
|
|
3957
3962
|
const violations = [];
|
|
@@ -3968,7 +3973,7 @@ function validatePrincipleHierarchySections(filePath, content) {
|
|
|
3968
3973
|
function extractPrincipleRelationships(filePath, content) {
|
|
3969
3974
|
const lines = content.split(`
|
|
3970
3975
|
`);
|
|
3971
|
-
const fileDir =
|
|
3976
|
+
const fileDir = dirname4(filePath);
|
|
3972
3977
|
const parentPrinciples = [];
|
|
3973
3978
|
const subPrinciples = [];
|
|
3974
3979
|
let currentSection = null;
|
|
@@ -4089,7 +4094,7 @@ async function lintMarkdown(dependencies) {
|
|
|
4089
4094
|
const violations = [];
|
|
4090
4095
|
context.stdout("Validating directory structure...");
|
|
4091
4096
|
violations.push(...await validateDirectoryStructure(dustPath, fileSystem, dependencies.settings.extraDirectories));
|
|
4092
|
-
const settingsPath =
|
|
4097
|
+
const settingsPath = join8(dustPath, "config", "settings.json");
|
|
4093
4098
|
if (fileSystem.exists(settingsPath)) {
|
|
4094
4099
|
context.stdout("Validating settings.json...");
|
|
4095
4100
|
try {
|
|
@@ -4419,7 +4424,7 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
4419
4424
|
function generateHelpText(settings) {
|
|
4420
4425
|
const bin = settings.dustCommand;
|
|
4421
4426
|
return dedent`
|
|
4422
|
-
|
|
4427
|
+
✨ dust - Flow state for AI coding agents.
|
|
4423
4428
|
|
|
4424
4429
|
Usage: ${bin} <command> [options]
|
|
4425
4430
|
|
|
@@ -5243,6 +5248,9 @@ var COMMANDS = Object.keys(commandRegistry).filter((cmd) => !cmd.includes(" "));
|
|
|
5243
5248
|
function isHelpRequest(command) {
|
|
5244
5249
|
return !command || command === "help" || command === "--help" || command === "-h";
|
|
5245
5250
|
}
|
|
5251
|
+
function isVersionRequest(command) {
|
|
5252
|
+
return command === "--version" || command === "-v";
|
|
5253
|
+
}
|
|
5246
5254
|
function isValidCommand(command) {
|
|
5247
5255
|
return command in commandRegistry;
|
|
5248
5256
|
}
|
|
@@ -5262,6 +5270,10 @@ async function main(options) {
|
|
|
5262
5270
|
const { commandArguments, context, fileSystem, glob, directoryFileSorter } = options;
|
|
5263
5271
|
const settings = await loadSettings(context.cwd, fileSystem);
|
|
5264
5272
|
const helpText = generateHelpText(settings);
|
|
5273
|
+
if (isVersionRequest(commandArguments[0])) {
|
|
5274
|
+
context.stdout(DUST_VERSION);
|
|
5275
|
+
return { exitCode: 0 };
|
|
5276
|
+
}
|
|
5265
5277
|
if (isHelpRequest(commandArguments[0])) {
|
|
5266
5278
|
context.stdout(helpText);
|
|
5267
5279
|
return { exitCode: 0 };
|
|
@@ -5350,4 +5362,4 @@ await wireEntry({ existsSync, statSync: statSync2, readFile: readFile2, writeFil
|
|
|
5350
5362
|
process.stdout.write(message);
|
|
5351
5363
|
},
|
|
5352
5364
|
error: console.error
|
|
5353
|
-
});
|
|
5365
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem abstraction types used across the codebase.
|
|
3
|
+
*/
|
|
4
|
+
export interface WriteOptions {
|
|
5
|
+
flag?: 'w' | 'wx';
|
|
6
|
+
}
|
|
7
|
+
export interface ReadableFileSystem {
|
|
8
|
+
exists: (path: string) => boolean;
|
|
9
|
+
readFile: (path: string) => Promise<string>;
|
|
10
|
+
readdir: (path: string) => Promise<string[]>;
|
|
11
|
+
isDirectory: (path: string) => boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface FileSystem extends ReadableFileSystem {
|
|
14
|
+
writeFile: (path: string, content: string, options?: WriteOptions) => Promise<void>;
|
|
15
|
+
mkdir: (path: string, options?: {
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
}) => Promise<void>;
|
|
18
|
+
chmod: (path: string, mode: number) => Promise<void>;
|
|
19
|
+
getFileCreationTime: (path: string) => number;
|
|
20
|
+
rename: (oldPath: string, newPath: string) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export interface GlobScanner {
|
|
23
|
+
scan: (dir: string) => AsyncIterable<string>;
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshski/dust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
4
4
|
"description": "Flow state for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,13 @@
|
|
|
22
22
|
"import": "./dist/artifacts.js",
|
|
23
23
|
"types": "./dist/artifacts/index.d.ts"
|
|
24
24
|
},
|
|
25
|
+
"./audits": {
|
|
26
|
+
"import": "./dist/audits.js",
|
|
27
|
+
"types": "./dist/audits/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./filesystem": {
|
|
30
|
+
"types": "./dist/filesystem/types.d.ts"
|
|
31
|
+
},
|
|
25
32
|
"./istanbul/minimal-reporter": "./lib/istanbul/minimal-reporter.cjs"
|
|
26
33
|
},
|
|
27
34
|
"files": [
|
|
@@ -42,7 +49,7 @@
|
|
|
42
49
|
"author": "joshski",
|
|
43
50
|
"license": "MIT",
|
|
44
51
|
"scripts": {
|
|
45
|
-
"build": "bun
|
|
52
|
+
"build": "bun run scripts/build.ts",
|
|
46
53
|
"test": "vitest run",
|
|
47
54
|
"test:coverage": "vitest run --coverage",
|
|
48
55
|
"eval": "bun run ./evals/run.ts"
|