@joshski/dust 0.1.35 → 0.1.37
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 +172 -112
- package/dist/workflow-tasks.d.ts +7 -1
- package/dist/workflow-tasks.js +51 -7
- package/package.json +1 -1
- package/templates/agent-new-task.txt +1 -0
- package/templates/agent-greeting.txt +0 -31
- package/templates/agent-implement-task.txt +0 -26
- package/templates/agent-new-goal.txt +0 -22
- package/templates/agent-new-idea.txt +0 -46
- package/templates/agent-pick-task.txt +0 -7
- package/templates/agents-md.txt +0 -5
- package/templates/audits/dead-code.md +0 -30
- package/templates/audits/security-review.md +0 -30
- package/templates/audits/test-coverage.md +0 -29
- package/templates/claude-md.txt +0 -5
- package/templates/help.txt +0 -36
package/dist/dust.js
CHANGED
|
@@ -578,7 +578,24 @@ async function clearToken(fileSystem, homeDir) {
|
|
|
578
578
|
await fileSystem.writeFile(path, "{}");
|
|
579
579
|
} catch {}
|
|
580
580
|
}
|
|
581
|
+
async function defaultExchangeCode(code) {
|
|
582
|
+
const host = getDustbucketHost();
|
|
583
|
+
const response = await fetch(`${host}/auth/cli/exchange`, {
|
|
584
|
+
method: "POST",
|
|
585
|
+
headers: { "Content-Type": "application/json" },
|
|
586
|
+
body: JSON.stringify({ code })
|
|
587
|
+
});
|
|
588
|
+
if (!response.ok) {
|
|
589
|
+
throw new Error(`Token exchange failed: ${response.status}`);
|
|
590
|
+
}
|
|
591
|
+
const body = await response.json();
|
|
592
|
+
if (typeof body.token !== "string") {
|
|
593
|
+
throw new Error("Invalid token exchange response");
|
|
594
|
+
}
|
|
595
|
+
return body.token;
|
|
596
|
+
}
|
|
581
597
|
async function authenticate(authDeps) {
|
|
598
|
+
const exchange = authDeps.exchangeCode ?? defaultExchangeCode;
|
|
582
599
|
return new Promise((resolve, reject) => {
|
|
583
600
|
let timer = null;
|
|
584
601
|
let serverHandle = null;
|
|
@@ -595,13 +612,13 @@ async function authenticate(authDeps) {
|
|
|
595
612
|
const handler = (request) => {
|
|
596
613
|
const url = new URL(request.url);
|
|
597
614
|
if (url.pathname === "/callback") {
|
|
598
|
-
const
|
|
599
|
-
if (
|
|
615
|
+
const code = url.searchParams.get("code");
|
|
616
|
+
if (code) {
|
|
600
617
|
cleanup();
|
|
601
|
-
|
|
618
|
+
exchange(code).then(resolve, reject);
|
|
602
619
|
return new Response("<html><body><p>Authentication successful! You can close this tab.</p></body></html>", { headers: { "Content-Type": "text/html" } });
|
|
603
620
|
}
|
|
604
|
-
return new Response("Missing
|
|
621
|
+
return new Response("Missing code", { status: 400 });
|
|
605
622
|
}
|
|
606
623
|
return new Response("Not found", { status: 404 });
|
|
607
624
|
};
|
|
@@ -676,22 +693,20 @@ function getLogLines(buffer) {
|
|
|
676
693
|
import { join as join7 } from "node:path";
|
|
677
694
|
|
|
678
695
|
// lib/agent-events.ts
|
|
679
|
-
function
|
|
696
|
+
function rawEventToAgentEvent(rawEvent) {
|
|
697
|
+
if (typeof rawEvent.type === "string" && rawEvent.type === "stream_event") {
|
|
698
|
+
return { type: "agent-session-activity" };
|
|
699
|
+
}
|
|
700
|
+
return { type: "claude-event", rawEvent };
|
|
701
|
+
}
|
|
702
|
+
function formatAgentEvent(event) {
|
|
680
703
|
switch (event.type) {
|
|
681
|
-
case "
|
|
682
|
-
return
|
|
683
|
-
case "
|
|
684
|
-
return {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
error: event.error
|
|
688
|
-
};
|
|
689
|
-
case "claude.raw_event":
|
|
690
|
-
if (typeof event.rawEvent.type === "string" && event.rawEvent.type === "stream_event") {
|
|
691
|
-
return { type: "agent-session-activity" };
|
|
692
|
-
}
|
|
693
|
-
return { type: "claude-event", rawEvent: event.rawEvent };
|
|
694
|
-
default:
|
|
704
|
+
case "agent-session-started":
|
|
705
|
+
return "\uD83E\uDD16 Starting Claude...";
|
|
706
|
+
case "agent-session-ended":
|
|
707
|
+
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
708
|
+
case "agent-session-activity":
|
|
709
|
+
case "claude-event":
|
|
695
710
|
return null;
|
|
696
711
|
}
|
|
697
712
|
}
|
|
@@ -1098,6 +1113,42 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
1098
1113
|
// lib/cli/commands/loop.ts
|
|
1099
1114
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1100
1115
|
|
|
1116
|
+
// lib/cli/commands/focus.ts
|
|
1117
|
+
function buildImplementationInstructions(bin, hooksInstalled) {
|
|
1118
|
+
const steps = [];
|
|
1119
|
+
let step = 1;
|
|
1120
|
+
steps.push(`${step}. Run \`${bin} check\` to verify the project is in a good state`);
|
|
1121
|
+
step++;
|
|
1122
|
+
steps.push(`${step}. Implement the task`);
|
|
1123
|
+
step++;
|
|
1124
|
+
if (!hooksInstalled) {
|
|
1125
|
+
steps.push(`${step}. Run \`${bin} check\` before committing`);
|
|
1126
|
+
step++;
|
|
1127
|
+
}
|
|
1128
|
+
steps.push(`${step}. Create a single atomic commit that includes:`, " - All implementation changes", " - Deletion of the completed task file", " - Updates to any facts that changed", " - Deletion of the idea file that spawned this task (if remaining scope exists, create new ideas for it)", "", ' Use the task title as the commit message. Task titles are written in imperative form, which is the recommended style for git commit messages. Do not add prefixes like "Complete task:" - use the title directly.', "", ' Example: If the task title is "Add validation for user input", the commit message should be:', " ```", " Add validation for user input", " ```", "");
|
|
1129
|
+
step++;
|
|
1130
|
+
steps.push(`${step}. Push your commit to the remote repository`);
|
|
1131
|
+
steps.push("");
|
|
1132
|
+
steps.push("Keep your change small and focused. One task, one commit.");
|
|
1133
|
+
return steps.join(`
|
|
1134
|
+
`);
|
|
1135
|
+
}
|
|
1136
|
+
async function focus(dependencies) {
|
|
1137
|
+
const { context, settings } = dependencies;
|
|
1138
|
+
const objective = dependencies.arguments.join(" ").trim();
|
|
1139
|
+
if (!objective) {
|
|
1140
|
+
context.stderr("Error: No objective provided");
|
|
1141
|
+
context.stderr('Usage: dust focus "your objective here"');
|
|
1142
|
+
return { exitCode: 1 };
|
|
1143
|
+
}
|
|
1144
|
+
const hooksInstalled = await manageGitHooks(dependencies);
|
|
1145
|
+
const vars = templateVariables(settings, hooksInstalled);
|
|
1146
|
+
context.stdout(`\uD83C\uDFAF Focus: ${objective}`);
|
|
1147
|
+
context.stdout("");
|
|
1148
|
+
context.stdout(buildImplementationInstructions(vars.bin, hooksInstalled));
|
|
1149
|
+
return { exitCode: 0 };
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1101
1152
|
// lib/cli/commands/next.ts
|
|
1102
1153
|
function extractBlockedBy(content) {
|
|
1103
1154
|
const blockedByMatch = content.match(/^## Blocked By\s*\n([\s\S]*?)(?=\n## |\n*$)/m);
|
|
@@ -1178,7 +1229,7 @@ async function next(dependencies) {
|
|
|
1178
1229
|
}
|
|
1179
1230
|
|
|
1180
1231
|
// lib/cli/commands/loop.ts
|
|
1181
|
-
function
|
|
1232
|
+
function formatLoopEvent(event) {
|
|
1182
1233
|
switch (event.type) {
|
|
1183
1234
|
case "loop.warning":
|
|
1184
1235
|
return "⚠️ WARNING: This command skips all permission checks. Only use in a sandbox environment!";
|
|
@@ -1196,16 +1247,12 @@ function formatEvent(event) {
|
|
|
1196
1247
|
case "loop.tasks_found":
|
|
1197
1248
|
return `✨ Found a task. Going to work!
|
|
1198
1249
|
`;
|
|
1199
|
-
case "claude.started":
|
|
1200
|
-
return "\uD83E\uDD16 Starting Claude...";
|
|
1201
|
-
case "claude.ended":
|
|
1202
|
-
return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
|
|
1203
|
-
case "claude.raw_event":
|
|
1204
|
-
return null;
|
|
1205
1250
|
case "loop.iteration_complete":
|
|
1206
1251
|
return `\uD83D\uDCCB Completed iteration ${event.iteration}/${event.maxIterations}`;
|
|
1207
1252
|
case "loop.ended":
|
|
1208
1253
|
return `\uD83C\uDFC1 Reached max iterations (${event.maxIterations}). Exiting.`;
|
|
1254
|
+
case "loop.start_agent":
|
|
1255
|
+
return null;
|
|
1209
1256
|
}
|
|
1210
1257
|
}
|
|
1211
1258
|
async function defaultPostEvent(url, payload) {
|
|
@@ -1223,21 +1270,18 @@ function createDefaultDependencies() {
|
|
|
1223
1270
|
postEvent: defaultPostEvent
|
|
1224
1271
|
};
|
|
1225
1272
|
}
|
|
1226
|
-
function
|
|
1273
|
+
function createWireEventSender(eventsUrl, sessionId, postEvent, onError, getAgentSessionId, repository = "") {
|
|
1227
1274
|
let sequence = 0;
|
|
1228
1275
|
return (event) => {
|
|
1229
1276
|
if (!eventsUrl)
|
|
1230
1277
|
return;
|
|
1231
|
-
const agentEvent = mapToAgentEvent(event);
|
|
1232
|
-
if (!agentEvent)
|
|
1233
|
-
return;
|
|
1234
1278
|
sequence++;
|
|
1235
1279
|
const payload = {
|
|
1236
1280
|
sequence,
|
|
1237
1281
|
timestamp: new Date().toISOString(),
|
|
1238
1282
|
sessionId,
|
|
1239
1283
|
repository,
|
|
1240
|
-
event
|
|
1284
|
+
event
|
|
1241
1285
|
};
|
|
1242
1286
|
const agentSessionId = getAgentSessionId?.();
|
|
1243
1287
|
if (agentSessionId) {
|
|
@@ -1270,29 +1314,23 @@ async function gitPull(cwd, spawn) {
|
|
|
1270
1314
|
});
|
|
1271
1315
|
});
|
|
1272
1316
|
}
|
|
1273
|
-
async function
|
|
1274
|
-
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
stdout: () => {
|
|
1278
|
-
hasOutput = true;
|
|
1279
|
-
}
|
|
1280
|
-
};
|
|
1281
|
-
await next({ ...dependencies, context: captureContext });
|
|
1282
|
-
return hasOutput;
|
|
1317
|
+
async function findAvailableTasks(dependencies) {
|
|
1318
|
+
const { context, fileSystem } = dependencies;
|
|
1319
|
+
const result = await findUnblockedTasks(context.cwd, fileSystem);
|
|
1320
|
+
return result.tasks;
|
|
1283
1321
|
}
|
|
1284
|
-
async function runOneIteration(dependencies, loopDependencies,
|
|
1322
|
+
async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, options = {}) {
|
|
1285
1323
|
const { context } = dependencies;
|
|
1286
1324
|
const { spawn, run: run2 } = loopDependencies;
|
|
1287
1325
|
const { onRawEvent } = options;
|
|
1288
|
-
|
|
1326
|
+
onLoopEvent({ type: "loop.syncing" });
|
|
1289
1327
|
const pullResult = await gitPull(context.cwd, spawn);
|
|
1290
1328
|
if (!pullResult.success) {
|
|
1291
|
-
|
|
1329
|
+
onLoopEvent({
|
|
1292
1330
|
type: "loop.sync_skipped",
|
|
1293
|
-
reason: pullResult.message
|
|
1331
|
+
reason: pullResult.message
|
|
1294
1332
|
});
|
|
1295
|
-
|
|
1333
|
+
onAgentEvent?.({ type: "agent-session-started" });
|
|
1296
1334
|
const prompt2 = `git pull failed with the following error:
|
|
1297
1335
|
|
|
1298
1336
|
${pullResult.message}
|
|
@@ -1303,6 +1341,7 @@ Please resolve this issue. Common approaches:
|
|
|
1303
1341
|
3. After resolving, commit any changes and push to remote
|
|
1304
1342
|
|
|
1305
1343
|
Make sure the repository is in a clean state and synced with remote before finishing.`;
|
|
1344
|
+
onLoopEvent({ type: "loop.start_agent", prompt: prompt2 });
|
|
1306
1345
|
try {
|
|
1307
1346
|
await run2(prompt2, {
|
|
1308
1347
|
spawnOptions: {
|
|
@@ -1312,24 +1351,44 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
1312
1351
|
},
|
|
1313
1352
|
onRawEvent
|
|
1314
1353
|
});
|
|
1315
|
-
|
|
1354
|
+
onAgentEvent?.({ type: "agent-session-ended", success: true });
|
|
1316
1355
|
return "resolved_pull_conflict";
|
|
1317
1356
|
} catch (error) {
|
|
1318
1357
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1319
1358
|
context.stderr(`Claude failed to resolve git pull conflict: ${errorMessage}`);
|
|
1320
|
-
|
|
1359
|
+
onAgentEvent?.({
|
|
1360
|
+
type: "agent-session-ended",
|
|
1361
|
+
success: false,
|
|
1362
|
+
error: errorMessage
|
|
1363
|
+
});
|
|
1321
1364
|
}
|
|
1322
1365
|
}
|
|
1323
|
-
|
|
1324
|
-
const
|
|
1325
|
-
if (
|
|
1326
|
-
|
|
1366
|
+
onLoopEvent({ type: "loop.checking_tasks" });
|
|
1367
|
+
const tasks = await findAvailableTasks(dependencies);
|
|
1368
|
+
if (tasks.length === 0) {
|
|
1369
|
+
onLoopEvent({ type: "loop.no_tasks" });
|
|
1327
1370
|
return "no_tasks";
|
|
1328
1371
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1372
|
+
onLoopEvent({ type: "loop.tasks_found" });
|
|
1373
|
+
onAgentEvent?.({ type: "agent-session-started" });
|
|
1374
|
+
const task = tasks[0];
|
|
1375
|
+
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
1331
1376
|
const { dustCommand, installCommand = "npm install" } = dependencies.settings;
|
|
1332
|
-
const
|
|
1377
|
+
const instructions = buildImplementationInstructions(dustCommand, true);
|
|
1378
|
+
const prompt = `Run \`${installCommand}\` to install dependencies, then implement the following task.
|
|
1379
|
+
|
|
1380
|
+
## Task: ${task.title}
|
|
1381
|
+
|
|
1382
|
+
The following is the contents of the task file \`${task.path}\`:
|
|
1383
|
+
|
|
1384
|
+
${taskContent}
|
|
1385
|
+
|
|
1386
|
+
When the task is complete, delete the task file \`${task.path}\`.
|
|
1387
|
+
|
|
1388
|
+
## Instructions
|
|
1389
|
+
|
|
1390
|
+
${instructions}`;
|
|
1391
|
+
onLoopEvent({ type: "loop.start_agent", prompt });
|
|
1333
1392
|
try {
|
|
1334
1393
|
await run2(prompt, {
|
|
1335
1394
|
spawnOptions: {
|
|
@@ -1339,12 +1398,16 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
1339
1398
|
},
|
|
1340
1399
|
onRawEvent
|
|
1341
1400
|
});
|
|
1342
|
-
|
|
1401
|
+
onAgentEvent?.({ type: "agent-session-ended", success: true });
|
|
1343
1402
|
return "ran_claude";
|
|
1344
1403
|
} catch (error) {
|
|
1345
1404
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1346
1405
|
context.stderr(`Claude exited with error: ${errorMessage}`);
|
|
1347
|
-
|
|
1406
|
+
onAgentEvent?.({
|
|
1407
|
+
type: "agent-session-ended",
|
|
1408
|
+
success: false,
|
|
1409
|
+
error: errorMessage
|
|
1410
|
+
});
|
|
1348
1411
|
return "claude_error";
|
|
1349
1412
|
}
|
|
1350
1413
|
}
|
|
@@ -1365,19 +1428,25 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
1365
1428
|
const eventsUrl = settings.eventsUrl;
|
|
1366
1429
|
const sessionId = crypto.randomUUID();
|
|
1367
1430
|
let agentSessionId;
|
|
1368
|
-
const
|
|
1431
|
+
const sendWireEvent = createWireEventSender(eventsUrl, sessionId, postEvent, (error) => {
|
|
1369
1432
|
const message = error instanceof Error ? error.message : String(error);
|
|
1370
1433
|
context.stderr(`Event POST failed: ${message}`);
|
|
1371
1434
|
}, () => agentSessionId);
|
|
1372
|
-
const
|
|
1373
|
-
const formatted =
|
|
1435
|
+
const onLoopEvent = (event) => {
|
|
1436
|
+
const formatted = formatLoopEvent(event);
|
|
1437
|
+
if (formatted !== null) {
|
|
1438
|
+
context.stdout(formatted);
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
const onAgentEvent = (event) => {
|
|
1442
|
+
const formatted = formatAgentEvent(event);
|
|
1374
1443
|
if (formatted !== null) {
|
|
1375
1444
|
context.stdout(formatted);
|
|
1376
1445
|
}
|
|
1377
|
-
|
|
1446
|
+
sendWireEvent(event);
|
|
1378
1447
|
};
|
|
1379
|
-
|
|
1380
|
-
|
|
1448
|
+
onLoopEvent({ type: "loop.warning" });
|
|
1449
|
+
onLoopEvent({ type: "loop.started", maxIterations });
|
|
1381
1450
|
context.stdout(" Press Ctrl+C to stop");
|
|
1382
1451
|
context.stdout("");
|
|
1383
1452
|
let completedIterations = 0;
|
|
@@ -1387,24 +1456,24 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
|
|
|
1387
1456
|
if (typeof rawEvent.session_id === "string" && rawEvent.session_id) {
|
|
1388
1457
|
agentSessionId = rawEvent.session_id;
|
|
1389
1458
|
}
|
|
1390
|
-
|
|
1459
|
+
onAgentEvent(rawEventToAgentEvent(rawEvent));
|
|
1391
1460
|
};
|
|
1392
1461
|
}
|
|
1393
1462
|
while (completedIterations < maxIterations) {
|
|
1394
1463
|
agentSessionId = undefined;
|
|
1395
|
-
const result = await runOneIteration(dependencies, loopDependencies,
|
|
1464
|
+
const result = await runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, iterationOptions);
|
|
1396
1465
|
if (result === "no_tasks") {
|
|
1397
1466
|
await loopDependencies.sleep(SLEEP_INTERVAL_MS);
|
|
1398
1467
|
} else {
|
|
1399
1468
|
completedIterations++;
|
|
1400
|
-
|
|
1469
|
+
onLoopEvent({
|
|
1401
1470
|
type: "loop.iteration_complete",
|
|
1402
1471
|
iteration: completedIterations,
|
|
1403
1472
|
maxIterations
|
|
1404
1473
|
});
|
|
1405
1474
|
}
|
|
1406
1475
|
}
|
|
1407
|
-
|
|
1476
|
+
onLoopEvent({ type: "loop.ended", maxIterations });
|
|
1408
1477
|
return { exitCode: 0 };
|
|
1409
1478
|
}
|
|
1410
1479
|
|
|
@@ -1517,20 +1586,30 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
1517
1586
|
};
|
|
1518
1587
|
let agentSessionId;
|
|
1519
1588
|
let sequence = 0;
|
|
1520
|
-
const
|
|
1521
|
-
const formatted =
|
|
1589
|
+
const onLoopEvent = (event) => {
|
|
1590
|
+
const formatted = formatLoopEvent(event);
|
|
1522
1591
|
if (formatted !== null) {
|
|
1523
1592
|
appendLogLine(repoState.logBuffer, createLogLine(formatted, "stdout"));
|
|
1524
1593
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1594
|
+
};
|
|
1595
|
+
const onAgentEvent = (event) => {
|
|
1596
|
+
if (event.type === "agent-session-started") {
|
|
1597
|
+
repoState.agentStatus = "busy";
|
|
1598
|
+
} else if (event.type === "agent-session-ended") {
|
|
1599
|
+
repoState.agentStatus = "idle";
|
|
1600
|
+
}
|
|
1601
|
+
const formatted = formatAgentEvent(event);
|
|
1602
|
+
if (formatted !== null) {
|
|
1603
|
+
appendLogLine(repoState.logBuffer, createLogLine(formatted, "stdout"));
|
|
1604
|
+
}
|
|
1605
|
+
if (sendEvent && sessionId) {
|
|
1527
1606
|
sequence++;
|
|
1528
1607
|
const msg = {
|
|
1529
1608
|
sequence,
|
|
1530
1609
|
timestamp: new Date().toISOString(),
|
|
1531
1610
|
sessionId,
|
|
1532
1611
|
repository: repoName,
|
|
1533
|
-
event
|
|
1612
|
+
event
|
|
1534
1613
|
};
|
|
1535
1614
|
if (agentSessionId) {
|
|
1536
1615
|
msg.agentSessionId = agentSessionId;
|
|
@@ -1540,12 +1619,12 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
|
|
|
1540
1619
|
};
|
|
1541
1620
|
while (!repoState.stopRequested) {
|
|
1542
1621
|
agentSessionId = undefined;
|
|
1543
|
-
const result = await runOneIteration(commandDeps, loopDeps,
|
|
1622
|
+
const result = await runOneIteration(commandDeps, loopDeps, onLoopEvent, onAgentEvent, {
|
|
1544
1623
|
onRawEvent: (rawEvent) => {
|
|
1545
1624
|
if (typeof rawEvent.session_id === "string" && rawEvent.session_id) {
|
|
1546
1625
|
agentSessionId = rawEvent.session_id;
|
|
1547
1626
|
}
|
|
1548
|
-
|
|
1627
|
+
onAgentEvent(rawEventToAgentEvent(rawEvent));
|
|
1549
1628
|
}
|
|
1550
1629
|
});
|
|
1551
1630
|
if (result === "no_tasks") {
|
|
@@ -1579,7 +1658,8 @@ async function addRepository(repository, manager, repoDeps, context) {
|
|
|
1579
1658
|
path: repoPath,
|
|
1580
1659
|
loopPromise: null,
|
|
1581
1660
|
stopRequested: false,
|
|
1582
|
-
logBuffer: manager.logBuffers.get(repository.name) ?? createLogBuffer()
|
|
1661
|
+
logBuffer: manager.logBuffers.get(repository.name) ?? createLogBuffer(),
|
|
1662
|
+
agentStatus: "idle"
|
|
1583
1663
|
};
|
|
1584
1664
|
manager.repositories.set(repository.name, repoState);
|
|
1585
1665
|
const addedEvent = {
|
|
@@ -1709,6 +1789,7 @@ function createTerminalUIState() {
|
|
|
1709
1789
|
repositories: [],
|
|
1710
1790
|
selectedIndex: -1,
|
|
1711
1791
|
logBuffers: new Map,
|
|
1792
|
+
agentStatuses: new Map,
|
|
1712
1793
|
scrollOffset: 0,
|
|
1713
1794
|
autoScroll: true,
|
|
1714
1795
|
width: 80,
|
|
@@ -1730,6 +1811,7 @@ function addRepository2(state, name, logBuffer) {
|
|
|
1730
1811
|
return -1;
|
|
1731
1812
|
return a.localeCompare(b);
|
|
1732
1813
|
});
|
|
1814
|
+
state.agentStatuses.set(name, "idle");
|
|
1733
1815
|
}
|
|
1734
1816
|
state.logBuffers.set(name, logBuffer);
|
|
1735
1817
|
}
|
|
@@ -1738,6 +1820,7 @@ function removeRepository2(state, name) {
|
|
|
1738
1820
|
if (index >= 0) {
|
|
1739
1821
|
state.repositories.splice(index, 1);
|
|
1740
1822
|
state.logBuffers.delete(name);
|
|
1823
|
+
state.agentStatuses.delete(name);
|
|
1741
1824
|
if (state.selectedIndex >= state.repositories.length) {
|
|
1742
1825
|
state.selectedIndex = state.repositories.length - 1;
|
|
1743
1826
|
}
|
|
@@ -1788,7 +1871,7 @@ function getTabRowCount(state) {
|
|
|
1788
1871
|
return 1;
|
|
1789
1872
|
const tabWidths = [5];
|
|
1790
1873
|
for (const name of state.repositories) {
|
|
1791
|
-
tabWidths.push(name.length +
|
|
1874
|
+
tabWidths.push(name.length + 4);
|
|
1792
1875
|
}
|
|
1793
1876
|
let rows = 1;
|
|
1794
1877
|
let currentRowWidth = 0;
|
|
@@ -1856,14 +1939,17 @@ function renderTabs(state) {
|
|
|
1856
1939
|
for (let i = 0;i < state.repositories.length; i++) {
|
|
1857
1940
|
const name = state.repositories[i];
|
|
1858
1941
|
const color = getRepoColor(name, i);
|
|
1859
|
-
const
|
|
1942
|
+
const agentStatus = state.agentStatuses.get(name) ?? "idle";
|
|
1943
|
+
const dotColor = agentStatus === "busy" ? ANSI.FG_GREEN : ANSI.DIM;
|
|
1944
|
+
const dot = `${dotColor}●${ANSI.RESET}`;
|
|
1945
|
+
const width = name.length + 4;
|
|
1860
1946
|
if (i === state.selectedIndex) {
|
|
1861
1947
|
tabs.push({
|
|
1862
|
-
text: `${ANSI.INVERSE}${color} ${name} ${ANSI.RESET}`,
|
|
1948
|
+
text: `${ANSI.INVERSE} ${dot}${ANSI.INVERSE}${color} ${name} ${ANSI.RESET}`,
|
|
1863
1949
|
width
|
|
1864
1950
|
});
|
|
1865
1951
|
} else {
|
|
1866
|
-
tabs.push({ text:
|
|
1952
|
+
tabs.push({ text: ` ${dot}${color} ${name} ${ANSI.RESET}`, width });
|
|
1867
1953
|
}
|
|
1868
1954
|
}
|
|
1869
1955
|
const rows = [[]];
|
|
@@ -2190,12 +2276,18 @@ function syncUIWithRepoList(state, repos) {
|
|
|
2190
2276
|
}
|
|
2191
2277
|
}
|
|
2192
2278
|
}
|
|
2279
|
+
function syncAgentStatuses(state) {
|
|
2280
|
+
for (const [name, repoState] of state.repositories) {
|
|
2281
|
+
state.ui.agentStatuses.set(name, repoState.agentStatus);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2193
2284
|
function syncTUI(state) {
|
|
2194
2285
|
const currentUIRepos = new Set(state.ui.repositories);
|
|
2195
2286
|
const currentRepos = new Set(state.repositories.keys());
|
|
2196
2287
|
for (const [name, repoState] of state.repositories) {
|
|
2197
2288
|
state.logBuffers.set(name, repoState.logBuffer);
|
|
2198
2289
|
addRepository2(state.ui, name, repoState.logBuffer);
|
|
2290
|
+
state.ui.agentStatuses.set(name, repoState.agentStatus);
|
|
2199
2291
|
}
|
|
2200
2292
|
for (const name of currentUIRepos) {
|
|
2201
2293
|
if (name !== "system" && !currentRepos.has(name)) {
|
|
@@ -2325,6 +2417,7 @@ function setupTUI(state, bucketDeps) {
|
|
|
2325
2417
|
});
|
|
2326
2418
|
const renderInterval = setInterval(() => {
|
|
2327
2419
|
if (!state.shuttingDown) {
|
|
2420
|
+
syncAgentStatuses(state);
|
|
2328
2421
|
bucketDeps.writeStdout(renderFrame(state.ui));
|
|
2329
2422
|
}
|
|
2330
2423
|
}, 100);
|
|
@@ -3243,39 +3336,6 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
|
|
|
3243
3336
|
return { exitCode };
|
|
3244
3337
|
}
|
|
3245
3338
|
|
|
3246
|
-
// lib/cli/commands/focus.ts
|
|
3247
|
-
async function focus(dependencies) {
|
|
3248
|
-
const { context, settings } = dependencies;
|
|
3249
|
-
const objective = dependencies.arguments.join(" ").trim();
|
|
3250
|
-
if (!objective) {
|
|
3251
|
-
context.stderr("Error: No objective provided");
|
|
3252
|
-
context.stderr('Usage: dust focus "your objective here"');
|
|
3253
|
-
return { exitCode: 1 };
|
|
3254
|
-
}
|
|
3255
|
-
const hooksInstalled = await manageGitHooks(dependencies);
|
|
3256
|
-
const vars = templateVariables(settings, hooksInstalled);
|
|
3257
|
-
context.stdout(`\uD83C\uDFAF Focus: ${objective}`);
|
|
3258
|
-
context.stdout("");
|
|
3259
|
-
const steps = [];
|
|
3260
|
-
let step = 1;
|
|
3261
|
-
steps.push(`${step}. Run \`${vars.bin} check\` to verify the project is in a good state`);
|
|
3262
|
-
step++;
|
|
3263
|
-
steps.push(`${step}. Implement the task`);
|
|
3264
|
-
step++;
|
|
3265
|
-
if (!hooksInstalled) {
|
|
3266
|
-
steps.push(`${step}. Run \`${vars.bin} check\` before committing`);
|
|
3267
|
-
step++;
|
|
3268
|
-
}
|
|
3269
|
-
steps.push(`${step}. Create a single atomic commit that includes:`, " - All implementation changes", " - Deletion of the completed task file", " - Updates to any facts that changed", " - Deletion of the idea file that spawned this task (if remaining scope exists, create new ideas for it)", "", ' Use the task title as the commit message. Task titles are written in imperative form, which is the recommended style for git commit messages. Do not add prefixes like "Complete task:" - use the title directly.', "", ' Example: If the task title is "Add validation for user input", the commit message should be:', " ```", " Add validation for user input", " ```", "");
|
|
3270
|
-
step++;
|
|
3271
|
-
steps.push(`${step}. Push your commit to the remote repository`);
|
|
3272
|
-
steps.push("");
|
|
3273
|
-
steps.push("Keep your change small and focused. One task, one commit.");
|
|
3274
|
-
context.stdout(steps.join(`
|
|
3275
|
-
`));
|
|
3276
|
-
return { exitCode: 0 };
|
|
3277
|
-
}
|
|
3278
|
-
|
|
3279
3339
|
// lib/cli/commands/help.ts
|
|
3280
3340
|
function generateHelpText(settings) {
|
|
3281
3341
|
return loadTemplate("help", { bin: settings.dustCommand });
|
package/dist/workflow-tasks.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FileSystem } from './cli/types';
|
|
2
2
|
export declare const IDEA_TRANSITION_PREFIXES: string[];
|
|
3
3
|
export declare const CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
4
|
+
export declare const BUILD_IDEA_PREFIX = "Build Idea: ";
|
|
4
5
|
export interface IdeaInProgress {
|
|
5
6
|
taskSlug: string;
|
|
6
7
|
ideaTitle: string;
|
|
@@ -8,6 +9,7 @@ export interface IdeaInProgress {
|
|
|
8
9
|
export interface ParsedCaptureIdeaTask {
|
|
9
10
|
ideaTitle: string;
|
|
10
11
|
ideaDescription: string;
|
|
12
|
+
buildItNow: boolean;
|
|
11
13
|
}
|
|
12
14
|
export declare function findAllCaptureIdeaTasks(fileSystem: FileSystem, dustPath: string): Promise<IdeaInProgress[]>;
|
|
13
15
|
/**
|
|
@@ -42,5 +44,9 @@ export interface DecomposeIdeaOptions {
|
|
|
42
44
|
export declare function createRefineIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
43
45
|
export declare function decomposeIdea(fileSystem: FileSystem, dustPath: string, options: DecomposeIdeaOptions): Promise<CreateIdeaTransitionTaskResult>;
|
|
44
46
|
export declare function createShelveIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string): Promise<CreateIdeaTransitionTaskResult>;
|
|
45
|
-
export declare function createCaptureIdeaTask(fileSystem: FileSystem, dustPath: string,
|
|
47
|
+
export declare function createCaptureIdeaTask(fileSystem: FileSystem, dustPath: string, options: {
|
|
48
|
+
title: string;
|
|
49
|
+
description: string;
|
|
50
|
+
buildItNow?: boolean;
|
|
51
|
+
}): Promise<CreateIdeaTransitionTaskResult>;
|
|
46
52
|
export declare function parseCaptureIdeaTask(fileSystem: FileSystem, dustPath: string, taskSlug: string): Promise<ParsedCaptureIdeaTask | null>;
|
package/dist/workflow-tasks.js
CHANGED
|
@@ -5,6 +5,7 @@ var IDEA_TRANSITION_PREFIXES = [
|
|
|
5
5
|
"Shelve Idea: "
|
|
6
6
|
];
|
|
7
7
|
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
8
|
+
var BUILD_IDEA_PREFIX = "Build Idea: ";
|
|
8
9
|
async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
|
|
9
10
|
const tasksPath = `${dustPath}/tasks`;
|
|
10
11
|
if (!fileSystem.exists(tasksPath))
|
|
@@ -22,6 +23,11 @@ async function findAllCaptureIdeaTasks(fileSystem, dustPath) {
|
|
|
22
23
|
taskSlug: file.replace(/\.md$/, ""),
|
|
23
24
|
ideaTitle: title.slice(CAPTURE_IDEA_PREFIX.length)
|
|
24
25
|
});
|
|
26
|
+
} else if (title.startsWith(BUILD_IDEA_PREFIX)) {
|
|
27
|
+
results.push({
|
|
28
|
+
taskSlug: file.replace(/\.md$/, ""),
|
|
29
|
+
ideaTitle: title.slice(BUILD_IDEA_PREFIX.length)
|
|
30
|
+
});
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
return results;
|
|
@@ -112,7 +118,7 @@ async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description)
|
|
|
112
118
|
], { description });
|
|
113
119
|
}
|
|
114
120
|
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 --
|
|
121
|
+
return createIdeaTask(fileSystem, dustPath, "Decompose Idea: ", options.ideaSlug, (ideaTitle) => `Create one or more well-defined tasks from this idea. Prefer smaller, narrowly scoped tasks that each deliver a thin but complete vertical slice of working software -- a path through the system that can be tested end-to-end -- rather than component-oriented tasks (like "add schema" or "build endpoint") that only work once all tasks are done. 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
122
|
"One or more new tasks are created in .dust/tasks/",
|
|
117
123
|
"Task's Goals section links to relevant goals from .dust/goals/",
|
|
118
124
|
"The original idea is deleted or updated to reflect remaining scope"
|
|
@@ -124,14 +130,44 @@ async function decomposeIdea(fileSystem, dustPath, options) {
|
|
|
124
130
|
async function createShelveIdeaTask(fileSystem, dustPath, ideaSlug, description) {
|
|
125
131
|
return createIdeaTask(fileSystem, dustPath, "Shelve Idea: ", ideaSlug, (ideaTitle) => `Archive this idea and remove it from the active backlog. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, ["Idea file is deleted", "Rationale is recorded in the commit message"], { description });
|
|
126
132
|
}
|
|
127
|
-
async function createCaptureIdeaTask(fileSystem, dustPath,
|
|
133
|
+
async function createCaptureIdeaTask(fileSystem, dustPath, options) {
|
|
134
|
+
const { title, description, buildItNow } = options;
|
|
128
135
|
if (!title || !title.trim()) {
|
|
129
136
|
throw new Error("title is required and must not be whitespace-only");
|
|
130
137
|
}
|
|
131
138
|
if (!description || !description.trim()) {
|
|
132
139
|
throw new Error("description is required and must not be whitespace-only");
|
|
133
140
|
}
|
|
134
|
-
|
|
141
|
+
if (buildItNow) {
|
|
142
|
+
const taskTitle2 = `${BUILD_IDEA_PREFIX}${title}`;
|
|
143
|
+
const filename2 = titleToFilename(taskTitle2);
|
|
144
|
+
const filePath2 = `${dustPath}/tasks/${filename2}`;
|
|
145
|
+
const content2 = `# ${taskTitle2}
|
|
146
|
+
|
|
147
|
+
Research this idea thoroughly, review \`.dust/goals/\` and \`.dust/facts/\` for relevant context, then create one or more narrowly-scoped task files in \`.dust/tasks/\`. Each task should deliver a thin but complete vertical slice of working software.
|
|
148
|
+
|
|
149
|
+
## Idea Description
|
|
150
|
+
|
|
151
|
+
${description}
|
|
152
|
+
|
|
153
|
+
## Goals
|
|
154
|
+
|
|
155
|
+
(none)
|
|
156
|
+
|
|
157
|
+
## Blocked By
|
|
158
|
+
|
|
159
|
+
(none)
|
|
160
|
+
|
|
161
|
+
## Definition of Done
|
|
162
|
+
|
|
163
|
+
- [ ] One or more new tasks are created in \`.dust/tasks/\`
|
|
164
|
+
- [ ] Tasks link to relevant goals from \`.dust/goals/\`
|
|
165
|
+
- [ ] Tasks are narrowly scoped vertical slices
|
|
166
|
+
`;
|
|
167
|
+
await fileSystem.writeFile(filePath2, content2);
|
|
168
|
+
return { filePath: filePath2 };
|
|
169
|
+
}
|
|
170
|
+
const taskTitle = `${CAPTURE_IDEA_PREFIX}${title}`;
|
|
135
171
|
const filename = titleToFilename(taskTitle);
|
|
136
172
|
const filePath = `${dustPath}/tasks/${filename}`;
|
|
137
173
|
const ideaFilename = titleToFilename(title);
|
|
@@ -173,16 +209,23 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
|
|
|
173
209
|
return null;
|
|
174
210
|
}
|
|
175
211
|
const title = titleMatch[1].trim();
|
|
176
|
-
|
|
212
|
+
let ideaTitle;
|
|
213
|
+
let buildItNow;
|
|
214
|
+
if (title.startsWith(BUILD_IDEA_PREFIX)) {
|
|
215
|
+
ideaTitle = title.slice(BUILD_IDEA_PREFIX.length);
|
|
216
|
+
buildItNow = true;
|
|
217
|
+
} else if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
|
|
218
|
+
ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
|
|
219
|
+
buildItNow = false;
|
|
220
|
+
} else {
|
|
177
221
|
return null;
|
|
178
222
|
}
|
|
179
|
-
const ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
|
|
180
223
|
const descriptionMatch = content.match(/^## Idea Description\n\n([\s\S]*?)\n\n## /m);
|
|
181
224
|
if (!descriptionMatch) {
|
|
182
225
|
return null;
|
|
183
226
|
}
|
|
184
227
|
const ideaDescription = descriptionMatch[1];
|
|
185
|
-
return { ideaTitle, ideaDescription };
|
|
228
|
+
return { ideaTitle, ideaDescription, buildItNow };
|
|
186
229
|
}
|
|
187
230
|
export {
|
|
188
231
|
titleToFilename,
|
|
@@ -194,5 +237,6 @@ export {
|
|
|
194
237
|
createRefineIdeaTask,
|
|
195
238
|
createCaptureIdeaTask,
|
|
196
239
|
IDEA_TRANSITION_PREFIXES,
|
|
197
|
-
CAPTURE_IDEA_PREFIX
|
|
240
|
+
CAPTURE_IDEA_PREFIX,
|
|
241
|
+
BUILD_IDEA_PREFIX
|
|
198
242
|
};
|
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ Use a todo list to track your progress through these steps.
|
|
|
18
18
|
4. Create a new markdown file in `.dust/tasks/` with a descriptive kebab-case name (e.g., `add-user-authentication.md`)
|
|
19
19
|
5. Add a title as the first line using an H1 heading (e.g., `# Add user authentication`)
|
|
20
20
|
6. Write a comprehensive description starting with an imperative opening sentence (e.g., "Add caching to the API layer." not "This task adds caching."). Include technical details and references to relevant files.
|
|
21
|
+
- Scope the task as a **vertical slice**: a thin but complete path through the system that can be tested and built upon, rather than an isolated component or layer.
|
|
21
22
|
7. Add a `## Goals` section with links to relevant goals this task supports (e.g., `- [Goal Name](../goals/goal-name.md)`)
|
|
22
23
|
8. Add a `## Blocked By` section listing any tasks that must complete first, or `(none)` if there are no blockers
|
|
23
24
|
9. Add a `## Definition of Done` section with a checklist of completion criteria using `- [ ]` for each item
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
🤖 Hello {{agentName}}, welcome to dust!
|
|
2
|
-
|
|
3
|
-
CRITICAL: You MUST run exactly ONE of the commands below before doing anything else.
|
|
4
|
-
|
|
5
|
-
Determine the user's intent and run the matching command NOW:
|
|
6
|
-
|
|
7
|
-
1. **Pick up work from the backlog** → `{{bin}} pick task`
|
|
8
|
-
User wants to start working. Examples: "work", "go", "pick a task", "what's next?"
|
|
9
|
-
|
|
10
|
-
2. **Implement a specific task** → `{{bin}} focus "<task name>"`
|
|
11
|
-
User mentions a particular task by name. Examples: "implement the auth task", "work on caching"
|
|
12
|
-
|
|
13
|
-
3. **Capture a new task** → `{{bin}} new task`
|
|
14
|
-
User has concrete work to add. Keywords: "task: ..." or "add a task ..."
|
|
15
|
-
|
|
16
|
-
4. **Capture a new goal** → `{{bin}} new goal`
|
|
17
|
-
User has a higher-level objective to add. Keywords: "goal: ..." or "add a goal ..."
|
|
18
|
-
|
|
19
|
-
5. **Capture a vague idea** → `{{bin}} new idea`
|
|
20
|
-
User has a rough idea that might become work later. Keywords: "idea: ..." or "add an idea ..."
|
|
21
|
-
|
|
22
|
-
6. **Unclear** → `{{bin}} help`
|
|
23
|
-
If none of the above clearly apply, run this to see all available commands.
|
|
24
|
-
|
|
25
|
-
Do NOT proceed without running one of these commands.
|
|
26
|
-
{{#if agentInstructions}}
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
{{agentInstructions}}
|
|
31
|
-
{{/if}}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
## Implement a Task
|
|
2
|
-
|
|
3
|
-
{{#if isClaudeCodeWeb}}Follow these steps. Use a todo list to track your progress.
|
|
4
|
-
{{/if}}{{#unless isClaudeCodeWeb}}Follow these steps:
|
|
5
|
-
{{/unless}}
|
|
6
|
-
1. Run `{{bin}} next` to identify the (unblocked) task the user is referring to
|
|
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:
|
|
12
|
-
- All implementation changes
|
|
13
|
-
- Deletion of the completed task file
|
|
14
|
-
- Updates to any facts that changed
|
|
15
|
-
- Deletion of the idea file that spawned this task (if remaining scope exists, create new ideas for it)
|
|
16
|
-
|
|
17
|
-
Use the task title as the commit message. Task titles are written in imperative form, which is the recommended style for git commit messages. Do not add prefixes like "Complete task:" - use the title directly.
|
|
18
|
-
|
|
19
|
-
Example: If the task title is "Add validation for user input", the commit message should be:
|
|
20
|
-
```
|
|
21
|
-
Add validation for user input
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
{{#unless hooksInstalled}}7.{{/unless}}{{#if hooksInstalled}}6.{{/if}} Push your commit to the remote repository
|
|
25
|
-
|
|
26
|
-
Keep your change small and focused. One task, one commit.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
## Adding a New Goal
|
|
2
|
-
|
|
3
|
-
Goals are guiding principles that persist across tasks. They define the "why" behind the work.
|
|
4
|
-
|
|
5
|
-
{{#if isClaudeCodeWeb}}Follow these steps. Use a todo list to track your progress.
|
|
6
|
-
{{/if}}{{#unless isClaudeCodeWeb}}Follow these steps:
|
|
7
|
-
{{/unless}}
|
|
8
|
-
1. Run `{{bin}} goals` to see existing goals and avoid duplication
|
|
9
|
-
2. Create a new markdown file in `.dust/goals/` with a descriptive kebab-case name (e.g., `cross-platform-support.md`)
|
|
10
|
-
3. Add a title as the first line using an H1 heading (e.g., `# Cross-platform support`)
|
|
11
|
-
4. Write a clear description explaining:
|
|
12
|
-
- What this goal means in practice
|
|
13
|
-
- Why it matters for the project
|
|
14
|
-
- How to evaluate whether work supports this goal
|
|
15
|
-
5. Run `{{bin}} lint markdown` to catch any formatting issues
|
|
16
|
-
6. Create a single atomic commit with a message in the format "Add goal: <title>"
|
|
17
|
-
7. Push your commit to the remote repository
|
|
18
|
-
|
|
19
|
-
Goals should be:
|
|
20
|
-
- **Stable** - They rarely change once established
|
|
21
|
-
- **Actionable** - Tasks can be linked to them
|
|
22
|
-
- **Clear** - Anyone reading should understand what it means
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
## Adding a New Idea
|
|
2
|
-
|
|
3
|
-
Follow these steps:
|
|
4
|
-
|
|
5
|
-
1. Run `{{bin}} ideas` to see all existing ideas and avoid duplicates
|
|
6
|
-
2. Create a new markdown file in `.dust/ideas/` with a descriptive kebab-case name (e.g., `improve-error-messages.md`)
|
|
7
|
-
3. Add a title as the first line using an H1 heading (e.g., `# Improve error messages`)
|
|
8
|
-
4. Write a brief description of the potential change or improvement
|
|
9
|
-
5. If the idea has open questions, add an `## Open Questions` section (see below)
|
|
10
|
-
6. Run `{{bin}} lint markdown` to catch any issues with the idea file format
|
|
11
|
-
7. Create a single atomic commit with a message in the format "Add idea: <title>"
|
|
12
|
-
8. Push your commit to the remote repository
|
|
13
|
-
|
|
14
|
-
### Open Questions section
|
|
15
|
-
|
|
16
|
-
Ideas exist to eventually spawn tasks, so they start intentionally vague. An optional `## Open Questions` section captures the decisions that need to be made before the idea becomes actionable. Each question is an h3 heading ending with `?`, and each option is an h4 heading with markdown content explaining the trade-offs:
|
|
17
|
-
|
|
18
|
-
```markdown
|
|
19
|
-
## Open Questions
|
|
20
|
-
|
|
21
|
-
### Should we take our own payments?
|
|
22
|
-
|
|
23
|
-
#### Yes, take our own payments
|
|
24
|
-
|
|
25
|
-
Lower costs and we become the seller of record, but requires a merchant account.
|
|
26
|
-
|
|
27
|
-
#### No, use a payment provider
|
|
28
|
-
|
|
29
|
-
Higher costs but simpler setup. No merchant account needed.
|
|
30
|
-
|
|
31
|
-
### Which storage backend should we use?
|
|
32
|
-
|
|
33
|
-
#### SQLite
|
|
34
|
-
|
|
35
|
-
Simple and embedded. Good for single-node deployments.
|
|
36
|
-
|
|
37
|
-
#### PostgreSQL
|
|
38
|
-
|
|
39
|
-
Scalable but requires a separate server.
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Rules:
|
|
43
|
-
- Questions are `###` headings and must end with `?`
|
|
44
|
-
- Options are `####` headings beneath a question
|
|
45
|
-
- Each question must have at least one option
|
|
46
|
-
- Options can contain any markdown content (paragraphs, lists, code blocks, etc.)
|
package/templates/agents-md.txt
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Dead Code
|
|
2
|
-
|
|
3
|
-
Find and remove unused code to improve maintainability and reduce bundle size.
|
|
4
|
-
|
|
5
|
-
## Scope
|
|
6
|
-
|
|
7
|
-
Focus on these areas:
|
|
8
|
-
|
|
9
|
-
1. **Unused exports** - Functions, classes, constants that are never imported
|
|
10
|
-
2. **Unreachable code** - Code after return statements, impossible conditions
|
|
11
|
-
3. **Orphaned files** - Files that are not imported anywhere
|
|
12
|
-
4. **Unused dependencies** - Packages in package.json not used in code
|
|
13
|
-
5. **Commented-out code** - Old code left in comments
|
|
14
|
-
|
|
15
|
-
## Goals
|
|
16
|
-
|
|
17
|
-
(none)
|
|
18
|
-
|
|
19
|
-
## Blocked By
|
|
20
|
-
|
|
21
|
-
(none)
|
|
22
|
-
|
|
23
|
-
## Definition of Done
|
|
24
|
-
|
|
25
|
-
- [ ] Ran static analysis tools to find unused exports
|
|
26
|
-
- [ ] Identified files with no incoming imports
|
|
27
|
-
- [ ] Listed unused dependencies
|
|
28
|
-
- [ ] Reviewed commented-out code blocks
|
|
29
|
-
- [ ] Created list of code safe to remove
|
|
30
|
-
- [ ] Verified removal won't break dynamic imports or reflection
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Security Review
|
|
2
|
-
|
|
3
|
-
Review the codebase for common security vulnerabilities and misconfigurations.
|
|
4
|
-
|
|
5
|
-
## Scope
|
|
6
|
-
|
|
7
|
-
Focus on these areas:
|
|
8
|
-
|
|
9
|
-
1. **Hardcoded secrets** - API keys, passwords, tokens in source code
|
|
10
|
-
2. **Injection vulnerabilities** - SQL injection, command injection, XSS
|
|
11
|
-
3. **Authentication issues** - Weak password handling, missing auth checks
|
|
12
|
-
4. **Sensitive data exposure** - Logging sensitive data, insecure storage
|
|
13
|
-
5. **Dependency vulnerabilities** - Known CVEs in dependencies
|
|
14
|
-
|
|
15
|
-
## Goals
|
|
16
|
-
|
|
17
|
-
(none)
|
|
18
|
-
|
|
19
|
-
## Blocked By
|
|
20
|
-
|
|
21
|
-
(none)
|
|
22
|
-
|
|
23
|
-
## Definition of Done
|
|
24
|
-
|
|
25
|
-
- [ ] Searched for hardcoded secrets (API keys, passwords, tokens)
|
|
26
|
-
- [ ] Reviewed input validation and sanitization
|
|
27
|
-
- [ ] Checked authentication and authorization logic
|
|
28
|
-
- [ ] Verified sensitive data is not logged or exposed
|
|
29
|
-
- [ ] Ran dependency audit for known vulnerabilities
|
|
30
|
-
- [ ] Documented any findings with severity ratings
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Test Coverage
|
|
2
|
-
|
|
3
|
-
Identify untested code paths and areas that need additional test coverage.
|
|
4
|
-
|
|
5
|
-
## Scope
|
|
6
|
-
|
|
7
|
-
Focus on these areas:
|
|
8
|
-
|
|
9
|
-
1. **Core business logic** - Functions that handle critical operations
|
|
10
|
-
2. **Edge cases** - Boundary conditions, error handling paths
|
|
11
|
-
3. **Integration points** - API endpoints, database operations
|
|
12
|
-
4. **User-facing features** - UI components, form validation
|
|
13
|
-
5. **Recent changes** - Code modified in the last few commits
|
|
14
|
-
|
|
15
|
-
## Goals
|
|
16
|
-
|
|
17
|
-
(none)
|
|
18
|
-
|
|
19
|
-
## Blocked By
|
|
20
|
-
|
|
21
|
-
(none)
|
|
22
|
-
|
|
23
|
-
## Definition of Done
|
|
24
|
-
|
|
25
|
-
- [ ] Identified modules with low or no test coverage
|
|
26
|
-
- [ ] Listed critical paths that lack tests
|
|
27
|
-
- [ ] Prioritized areas by risk and importance
|
|
28
|
-
- [ ] Created list of specific test cases to add
|
|
29
|
-
- [ ] Estimated effort for improving coverage
|
package/templates/claude-md.txt
DELETED
package/templates/help.txt
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
💨 dust - Flow state for AI coding agents.
|
|
2
|
-
|
|
3
|
-
Usage: {{bin}} <command> [options]
|
|
4
|
-
|
|
5
|
-
Commands:
|
|
6
|
-
init Initialize a new Dust repository
|
|
7
|
-
lint markdown Run lint checks on .dust/ files
|
|
8
|
-
list List all items (tasks, ideas, goals, facts)
|
|
9
|
-
tasks List tasks (actionable work with definitions of done)
|
|
10
|
-
ideas List ideas (vague proposals, convert to tasks when ready)
|
|
11
|
-
goals List goals (guiding principles, stable, rarely change)
|
|
12
|
-
facts List facts (documentation of current system state)
|
|
13
|
-
next Show tasks ready to work on (not blocked)
|
|
14
|
-
check Run project-defined quality gate hook
|
|
15
|
-
agent Agent greeting and routing instructions
|
|
16
|
-
focus Declare current objective (for remote session tracking)
|
|
17
|
-
pick task Pick the next task to work on
|
|
18
|
-
implement task Implement a task
|
|
19
|
-
new task Create a new task
|
|
20
|
-
new goal Create a new goal
|
|
21
|
-
new idea Create a new idea
|
|
22
|
-
loop claude Run continuous Claude iteration on tasks
|
|
23
|
-
pre push Git pre-push hook validation
|
|
24
|
-
help Show this help message
|
|
25
|
-
|
|
26
|
-
🤖 Agent Guide
|
|
27
|
-
|
|
28
|
-
Dust is a lightweight planning system. The .dust/ directory contains:
|
|
29
|
-
- goals/ - Guiding principles (stable, rarely change)
|
|
30
|
-
- ideas/ - Proposals (convert to tasks when ready)
|
|
31
|
-
- tasks/ - Actionable work with definitions of done
|
|
32
|
-
- facts/ - Documentation of current system state
|
|
33
|
-
|
|
34
|
-
Workflow: Pick a task → implement it → delete the task file → commit atomically.
|
|
35
|
-
|
|
36
|
-
Run `{{bin}} agent` to get started!
|