@joshski/dust 0.1.30 → 0.1.32
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
|
@@ -435,7 +435,7 @@ function extractOpeningSentence(content) {
|
|
|
435
435
|
// lib/workflow-tasks.ts
|
|
436
436
|
var IDEA_TRANSITION_PREFIXES = [
|
|
437
437
|
"Refine Idea: ",
|
|
438
|
-
"
|
|
438
|
+
"Decompose Idea: ",
|
|
439
439
|
"Shelve Idea: "
|
|
440
440
|
];
|
|
441
441
|
function titleToFilename(title) {
|
|
@@ -589,6 +589,14 @@ function validateIdeaOpenQuestions(filePath, content) {
|
|
|
589
589
|
line: currentQuestionLine
|
|
590
590
|
});
|
|
591
591
|
}
|
|
592
|
+
const headingText = line.slice(3).trimEnd();
|
|
593
|
+
if (headingText.toLowerCase() === "open questions" && headingText !== "Open Questions") {
|
|
594
|
+
violations.push({
|
|
595
|
+
file: filePath,
|
|
596
|
+
message: `Heading "${line.trimEnd()}" should be "## Open Questions"`,
|
|
597
|
+
line: i + 1
|
|
598
|
+
});
|
|
599
|
+
}
|
|
592
600
|
inOpenQuestions = line === "## Open Questions";
|
|
593
601
|
currentQuestionLine = null;
|
|
594
602
|
continue;
|
|
@@ -1042,25 +1050,33 @@ async function lintMarkdown(dependencies) {
|
|
|
1042
1050
|
|
|
1043
1051
|
// lib/cli/commands/check.ts
|
|
1044
1052
|
var DEFAULT_CHECK_TIMEOUT_MS = 13000;
|
|
1053
|
+
async function runSingleCheck(check, cwd, runner) {
|
|
1054
|
+
const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
1055
|
+
const startTime = Date.now();
|
|
1056
|
+
const result = await runner.run(check.command, cwd, timeoutMs);
|
|
1057
|
+
const durationMs = Date.now() - startTime;
|
|
1058
|
+
return {
|
|
1059
|
+
name: check.name,
|
|
1060
|
+
command: check.command,
|
|
1061
|
+
exitCode: result.exitCode,
|
|
1062
|
+
output: result.output,
|
|
1063
|
+
hints: check.hints,
|
|
1064
|
+
durationMs,
|
|
1065
|
+
timedOut: result.timedOut,
|
|
1066
|
+
timeoutSeconds: timeoutMs / 1000
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1045
1069
|
async function runConfiguredChecks(checks, cwd, runner) {
|
|
1046
|
-
const promises = checks.map(
|
|
1047
|
-
const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
|
|
1048
|
-
const startTime = Date.now();
|
|
1049
|
-
const result = await runner.run(check.command, cwd, timeoutMs);
|
|
1050
|
-
const durationMs = Date.now() - startTime;
|
|
1051
|
-
return {
|
|
1052
|
-
name: check.name,
|
|
1053
|
-
command: check.command,
|
|
1054
|
-
exitCode: result.exitCode,
|
|
1055
|
-
output: result.output,
|
|
1056
|
-
hints: check.hints,
|
|
1057
|
-
durationMs,
|
|
1058
|
-
timedOut: result.timedOut,
|
|
1059
|
-
timeoutSeconds: timeoutMs / 1000
|
|
1060
|
-
};
|
|
1061
|
-
});
|
|
1070
|
+
const promises = checks.map((check) => runSingleCheck(check, cwd, runner));
|
|
1062
1071
|
return Promise.all(promises);
|
|
1063
1072
|
}
|
|
1073
|
+
async function runConfiguredChecksSerially(checks, cwd, runner) {
|
|
1074
|
+
const results = [];
|
|
1075
|
+
for (const check of checks) {
|
|
1076
|
+
results.push(await runSingleCheck(check, cwd, runner));
|
|
1077
|
+
}
|
|
1078
|
+
return results;
|
|
1079
|
+
}
|
|
1064
1080
|
async function runValidationCheck(dependencies) {
|
|
1065
1081
|
const outputLines = [];
|
|
1066
1082
|
const bufferedContext = {
|
|
@@ -1123,7 +1139,13 @@ function displayResults(results, context) {
|
|
|
1123
1139
|
return failed.length > 0 ? 1 : 0;
|
|
1124
1140
|
}
|
|
1125
1141
|
async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
1126
|
-
const {
|
|
1142
|
+
const {
|
|
1143
|
+
arguments: commandArguments,
|
|
1144
|
+
context,
|
|
1145
|
+
fileSystem,
|
|
1146
|
+
settings
|
|
1147
|
+
} = dependencies;
|
|
1148
|
+
const serial = commandArguments.includes("--serial");
|
|
1127
1149
|
if (!settings.checks || settings.checks.length === 0) {
|
|
1128
1150
|
context.stderr("Error: No checks configured in .dust/config/settings.json");
|
|
1129
1151
|
context.stderr("");
|
|
@@ -1136,9 +1158,20 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
1136
1158
|
context.stderr(" }");
|
|
1137
1159
|
return { exitCode: 1 };
|
|
1138
1160
|
}
|
|
1139
|
-
const checkPromises = [];
|
|
1140
1161
|
const dustPath = `${context.cwd}/.dust`;
|
|
1141
|
-
|
|
1162
|
+
const hasDustDir = fileSystem.exists(dustPath);
|
|
1163
|
+
if (serial) {
|
|
1164
|
+
const results2 = [];
|
|
1165
|
+
if (hasDustDir) {
|
|
1166
|
+
results2.push(await runValidationCheck(dependencies));
|
|
1167
|
+
}
|
|
1168
|
+
const configuredResults = await runConfiguredChecksSerially(settings.checks, context.cwd, shellRunner);
|
|
1169
|
+
results2.push(...configuredResults);
|
|
1170
|
+
const exitCode2 = displayResults(results2, context);
|
|
1171
|
+
return { exitCode: exitCode2 };
|
|
1172
|
+
}
|
|
1173
|
+
const checkPromises = [];
|
|
1174
|
+
if (hasDustDir) {
|
|
1142
1175
|
checkPromises.push(runValidationCheck(dependencies));
|
|
1143
1176
|
}
|
|
1144
1177
|
checkPromises.push(runConfiguredChecks(settings.checks, context.cwd, shellRunner));
|
package/dist/workflow-tasks.d.ts
CHANGED
|
@@ -16,12 +16,13 @@ export declare function findAllCaptureIdeaTasks(fileSystem: FileSystem, dustPath
|
|
|
16
16
|
* 6. Add .md extension
|
|
17
17
|
*/
|
|
18
18
|
export declare function titleToFilename(title: string): string;
|
|
19
|
-
export type WorkflowTaskType = 'refine' | '
|
|
19
|
+
export type WorkflowTaskType = 'refine' | 'decompose-idea' | 'shelve';
|
|
20
20
|
export interface WorkflowTaskMatch {
|
|
21
21
|
type: WorkflowTaskType;
|
|
22
|
+
ideaSlug: string;
|
|
22
23
|
taskSlug: string;
|
|
23
24
|
}
|
|
24
|
-
export declare function
|
|
25
|
+
export declare function findWorkflowTaskForIdea(fileSystem: FileSystem, dustPath: string, ideaSlug: string): Promise<WorkflowTaskMatch | null>;
|
|
25
26
|
export interface CreateIdeaTransitionTaskResult {
|
|
26
27
|
filePath: string;
|
|
27
28
|
}
|
|
@@ -29,12 +30,12 @@ export interface OpenQuestionResponse {
|
|
|
29
30
|
question: string;
|
|
30
31
|
chosenOption: string;
|
|
31
32
|
}
|
|
32
|
-
export interface
|
|
33
|
+
export interface DecomposeIdeaOptions {
|
|
33
34
|
ideaSlug: string;
|
|
34
35
|
description?: string;
|
|
35
36
|
openQuestionResponses?: OpenQuestionResponse[];
|
|
36
37
|
}
|
|
37
38
|
export declare function createRefineIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
38
|
-
export declare function
|
|
39
|
+
export declare function decomposeIdea(fileSystem: FileSystem, dustPath: string, options: DecomposeIdeaOptions): Promise<CreateIdeaTransitionTaskResult>;
|
|
39
40
|
export declare function createShelveIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
40
41
|
export declare function createCaptureIdeaTask(fileSystem: FileSystem, dustPath: string, title: string, description: string): Promise<CreateIdeaTransitionTaskResult>;
|
package/dist/workflow-tasks.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// lib/workflow-tasks.ts
|
|
2
2
|
var IDEA_TRANSITION_PREFIXES = [
|
|
3
3
|
"Refine Idea: ",
|
|
4
|
-
"
|
|
4
|
+
"Decompose Idea: ",
|
|
5
5
|
"Shelve Idea: "
|
|
6
6
|
];
|
|
7
7
|
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
@@ -31,17 +31,17 @@ function titleToFilename(title) {
|
|
|
31
31
|
}
|
|
32
32
|
var WORKFLOW_TASK_TYPES = [
|
|
33
33
|
{ type: "refine", prefix: "Refine Idea: " },
|
|
34
|
-
{ type: "
|
|
34
|
+
{ type: "decompose-idea", prefix: "Decompose Idea: " },
|
|
35
35
|
{ type: "shelve", prefix: "Shelve Idea: " }
|
|
36
36
|
];
|
|
37
|
-
async function
|
|
37
|
+
async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
|
|
38
38
|
const ideaTitle = await readIdeaTitle(fileSystem, dustPath, ideaSlug);
|
|
39
39
|
for (const { type, prefix } of WORKFLOW_TASK_TYPES) {
|
|
40
40
|
const filename = titleToFilename(`${prefix}${ideaTitle}`);
|
|
41
41
|
const filePath = `${dustPath}/tasks/${filename}`;
|
|
42
42
|
if (fileSystem.exists(filePath)) {
|
|
43
43
|
const taskSlug = filename.replace(/\.md$/, "");
|
|
44
|
-
return { type, taskSlug };
|
|
44
|
+
return { type, ideaSlug, taskSlug };
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
return null;
|
|
@@ -105,15 +105,16 @@ async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSen
|
|
|
105
105
|
return { filePath };
|
|
106
106
|
}
|
|
107
107
|
async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description) {
|
|
108
|
-
return createIdeaTask(fileSystem, dustPath, "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
|
|
108
|
+
return createIdeaTask(fileSystem, dustPath, "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Review \`.dust/goals/\` for alignment and \`.dust/facts/\` for relevant design decisions. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
|
|
109
109
|
"Idea is thoroughly researched with relevant codebase context",
|
|
110
110
|
"Open questions are added for any ambiguous or underspecified aspects",
|
|
111
111
|
"Idea file is updated with findings"
|
|
112
112
|
], { description });
|
|
113
113
|
}
|
|
114
|
-
async function
|
|
115
|
-
return createIdeaTask(fileSystem, dustPath, "
|
|
116
|
-
"
|
|
114
|
+
async function decomposeIdea(fileSystem, dustPath, options) {
|
|
115
|
+
return createIdeaTask(fileSystem, dustPath, "Decompose Idea: ", options.ideaSlug, (ideaTitle) => `Create one or more well-defined tasks from this idea. Prefer smaller, narrowly scoped tasks -- split the idea into multiple tasks if it covers more than one logical change. Review \`.dust/goals/\` to link relevant goals and \`.dust/facts/\` for design decisions that should inform the task. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
|
|
116
|
+
"One or more new tasks are created in .dust/tasks/",
|
|
117
|
+
"Task's Goals section links to relevant goals from .dust/goals/",
|
|
117
118
|
"The original idea is deleted or updated to reflect remaining scope"
|
|
118
119
|
], {
|
|
119
120
|
description: options.description,
|
|
@@ -135,7 +136,7 @@ async function createCaptureIdeaTask(fileSystem, dustPath, title, description) {
|
|
|
135
136
|
const filePath = `${dustPath}/tasks/${filename}`;
|
|
136
137
|
const ideaFilename = titleToFilename(title);
|
|
137
138
|
const ideaPath = `.dust/ideas/${ideaFilename}`;
|
|
138
|
-
const content = renderTask(taskTitle, `Research this idea thoroughly, then create an idea file at \`${ideaPath}\`. Read the codebase for relevant context, flesh out the description, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. The idea should have the title "${title}" and start from the following description:`, [
|
|
139
|
+
const content = renderTask(taskTitle, `Research this idea thoroughly, then create an idea file at \`${ideaPath}\`. Read the codebase for relevant context, flesh out the description, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Review \`.dust/goals/\` and \`.dust/facts/\` for relevant context. The idea should have the title "${title}" and start from the following description:`, [
|
|
139
140
|
`Idea file exists at ${ideaPath}`,
|
|
140
141
|
`Idea file has an H1 title matching "${title}"`,
|
|
141
142
|
"Idea includes relevant context from codebase exploration",
|
|
@@ -146,9 +147,9 @@ async function createCaptureIdeaTask(fileSystem, dustPath, title, description) {
|
|
|
146
147
|
}
|
|
147
148
|
export {
|
|
148
149
|
titleToFilename,
|
|
149
|
-
|
|
150
|
+
findWorkflowTaskForIdea,
|
|
150
151
|
findAllCaptureIdeaTasks,
|
|
151
|
-
|
|
152
|
+
decomposeIdea,
|
|
152
153
|
createShelveIdeaTask,
|
|
153
154
|
createRefineIdeaTask,
|
|
154
155
|
createCaptureIdeaTask,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { ReportBase } = require('istanbul-lib-report')
|
|
2
|
+
|
|
3
|
+
function isFull(metrics) {
|
|
4
|
+
return (
|
|
5
|
+
metrics.statements.pct === 100 &&
|
|
6
|
+
metrics.branches.pct === 100 &&
|
|
7
|
+
metrics.functions.pct === 100 &&
|
|
8
|
+
metrics.lines.pct === 100
|
|
9
|
+
)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatMetrics(metrics) {
|
|
13
|
+
const parts = []
|
|
14
|
+
if (metrics.lines.pct < 100) parts.push(`${metrics.lines.pct}% lines`)
|
|
15
|
+
if (metrics.statements.pct < 100)
|
|
16
|
+
parts.push(`${metrics.statements.pct}% statements`)
|
|
17
|
+
if (metrics.branches.pct < 100)
|
|
18
|
+
parts.push(`${metrics.branches.pct}% branches`)
|
|
19
|
+
if (metrics.functions.pct < 100)
|
|
20
|
+
parts.push(`${metrics.functions.pct}% functions`)
|
|
21
|
+
return parts.join(', ')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getUncoveredLines(fileCoverage) {
|
|
25
|
+
const lineCoverage = fileCoverage.getLineCoverage()
|
|
26
|
+
const ranges = []
|
|
27
|
+
let rangeStart = null
|
|
28
|
+
let rangeEnd = null
|
|
29
|
+
|
|
30
|
+
for (const [lineStr, hits] of Object.entries(lineCoverage)) {
|
|
31
|
+
const line = Number.parseInt(lineStr, 10)
|
|
32
|
+
if (hits === 0) {
|
|
33
|
+
if (rangeStart === null) {
|
|
34
|
+
rangeStart = line
|
|
35
|
+
rangeEnd = line
|
|
36
|
+
} else if (line === rangeEnd + 1) {
|
|
37
|
+
rangeEnd = line
|
|
38
|
+
} else {
|
|
39
|
+
ranges.push([rangeStart, rangeEnd])
|
|
40
|
+
rangeStart = line
|
|
41
|
+
rangeEnd = line
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (rangeStart !== null) {
|
|
47
|
+
ranges.push([rangeStart, rangeEnd])
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return ranges.map(([start, end]) =>
|
|
51
|
+
start === end ? `Line ${start}` : `Lines ${start}-${end}`
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class IncompleteCoverageReporter extends ReportBase {
|
|
56
|
+
execute(context) {
|
|
57
|
+
const incompleteFiles = []
|
|
58
|
+
context.getTree().visit({
|
|
59
|
+
onDetail(node) {
|
|
60
|
+
const metrics = node.getCoverageSummary()
|
|
61
|
+
if (!metrics.isEmpty() && !isFull(metrics)) {
|
|
62
|
+
incompleteFiles.push({
|
|
63
|
+
name: node.getQualifiedName(),
|
|
64
|
+
metrics,
|
|
65
|
+
fileCoverage: node.getFileCoverage(),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
if (incompleteFiles.length === 0) return
|
|
72
|
+
|
|
73
|
+
const cw = context.writer.writeFile(null)
|
|
74
|
+
const count = incompleteFiles.length
|
|
75
|
+
const label = count === 1 ? '1 file has' : `${count} files have`
|
|
76
|
+
cw.println(`${label} < 100% coverage:`)
|
|
77
|
+
|
|
78
|
+
for (const file of incompleteFiles) {
|
|
79
|
+
cw.println('')
|
|
80
|
+
cw.println(`${file.name} (${formatMetrics(file.metrics)})`)
|
|
81
|
+
for (const line of getUncoveredLines(file.fileCoverage)) {
|
|
82
|
+
cw.println(`- ${line}`)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
cw.close()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = IncompleteCoverageReporter
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshski/dust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "Flow state for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,12 +10,14 @@
|
|
|
10
10
|
"./workflow-tasks": {
|
|
11
11
|
"import": "./dist/workflow-tasks.js",
|
|
12
12
|
"types": "./dist/workflow-tasks.d.ts"
|
|
13
|
-
}
|
|
13
|
+
},
|
|
14
|
+
"./istanbul/minimal-reporter": "./lib/istanbul/minimal-reporter.cjs"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"dist",
|
|
17
18
|
"bin",
|
|
18
|
-
"templates"
|
|
19
|
+
"templates",
|
|
20
|
+
"lib/istanbul/minimal-reporter.cjs"
|
|
19
21
|
],
|
|
20
22
|
"repository": {
|
|
21
23
|
"type": "git",
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
🤖 Hello {{agentName}}, welcome to dust!
|
|
2
|
-
{{#if agentInstructions}}
|
|
3
|
-
|
|
4
|
-
## Project Instructions
|
|
5
|
-
|
|
6
|
-
{{agentInstructions}}
|
|
7
|
-
{{/if}}
|
|
8
2
|
|
|
9
3
|
CRITICAL: You MUST run exactly ONE of the commands below before doing anything else.
|
|
10
4
|
|
|
@@ -29,3 +23,9 @@ Determine the user's intent and run the matching command NOW:
|
|
|
29
23
|
If none of the above clearly apply, run this to see all available commands.
|
|
30
24
|
|
|
31
25
|
Do NOT proceed without running one of these commands.
|
|
26
|
+
{{#if agentInstructions}}
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
{{agentInstructions}}
|
|
31
|
+
{{/if}}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
🤖 Hello {{agentName}}, welcome to dust!
|
|
2
|
-
{{#if agentInstructions}}
|
|
3
|
-
|
|
4
|
-
## Project Instructions
|
|
5
|
-
|
|
6
|
-
{{agentInstructions}}
|
|
7
|
-
{{/if}}
|
|
8
2
|
|
|
9
3
|
CRITICAL: You MUST run exactly ONE of the commands below before doing anything else.
|
|
10
4
|
|
|
@@ -29,3 +23,9 @@ Determine the user's intent and run the matching command NOW:
|
|
|
29
23
|
If none of the above clearly apply, run this to see all available commands.
|
|
30
24
|
|
|
31
25
|
Do NOT proceed without running one of these commands.
|
|
26
|
+
{{#if agentInstructions}}
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
{{agentInstructions}}
|
|
31
|
+
{{/if}}
|