@joshski/dust 0.1.110 → 0.1.112
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/README.md +4 -0
- package/dist/artifacts/facts.d.ts +2 -5
- package/dist/artifacts/ideas.d.ts +2 -15
- package/dist/artifacts/index.d.ts +2 -38
- package/dist/artifacts/principles.d.ts +2 -7
- package/dist/artifacts/repository-principle-hierarchy.d.ts +16 -0
- package/dist/artifacts/tasks.d.ts +2 -8
- package/dist/artifacts/types.d.ts +98 -0
- package/dist/artifacts/workflow-tasks.d.ts +2 -16
- package/dist/artifacts.js +40 -4
- package/dist/bucket/repository-loop.d.ts +1 -1
- package/dist/bucket/repository-types.d.ts +58 -0
- package/dist/bucket/repository.d.ts +2 -52
- package/dist/bucket/server-messages.d.ts +8 -2
- package/dist/cli/types.d.ts +5 -1
- package/dist/core-principles.js +608 -608
- package/dist/dust.js +1302 -842
- package/dist/execution-order.d.ts +17 -0
- package/dist/execution-order.js +39 -0
- package/package.json +5 -1
package/dist/dust.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
7
7
|
var require_package = __commonJS((exports, module) => {
|
|
8
8
|
module.exports = {
|
|
9
9
|
name: "@joshski/dust",
|
|
10
|
-
version: "0.1.
|
|
10
|
+
version: "0.1.112",
|
|
11
11
|
description: "Flow state for AI coding agents",
|
|
12
12
|
type: "module",
|
|
13
13
|
bin: {
|
|
@@ -56,6 +56,10 @@ var require_package = __commonJS((exports, module) => {
|
|
|
56
56
|
"./core-principles": {
|
|
57
57
|
import: "./dist/core-principles.js",
|
|
58
58
|
types: "./dist/core-principles.d.ts"
|
|
59
|
+
},
|
|
60
|
+
"./execution-order": {
|
|
61
|
+
import: "./dist/execution-order.js",
|
|
62
|
+
types: "./dist/execution-order.d.ts"
|
|
59
63
|
}
|
|
60
64
|
},
|
|
61
65
|
files: [
|
|
@@ -394,16 +398,22 @@ import {
|
|
|
394
398
|
// lib/git/file-sorter.ts
|
|
395
399
|
function createGitDirectoryFileSorter(gitRunner) {
|
|
396
400
|
return async (dir, files) => {
|
|
397
|
-
const
|
|
401
|
+
const results = await Promise.all(files.map(async (file) => {
|
|
398
402
|
const result = await gitRunner.run(["log", "-1", "--format=%ct", "--", file], dir);
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
timestamp: Number.isNaN(ts) ? Number.POSITIVE_INFINITY : ts
|
|
403
|
-
};
|
|
403
|
+
const epochSeconds = result.exitCode === 0 ? Number.parseInt(result.output.trim(), 10) : Number.NaN;
|
|
404
|
+
const lastCommittedAt = Number.isNaN(epochSeconds) ? null : new Date(epochSeconds * 1000).toISOString();
|
|
405
|
+
return { file, lastCommittedAt };
|
|
404
406
|
}));
|
|
405
|
-
|
|
406
|
-
|
|
407
|
+
results.sort((a, b) => {
|
|
408
|
+
if (a.lastCommittedAt === null && b.lastCommittedAt === null)
|
|
409
|
+
return 0;
|
|
410
|
+
if (a.lastCommittedAt === null)
|
|
411
|
+
return 1;
|
|
412
|
+
if (b.lastCommittedAt === null)
|
|
413
|
+
return -1;
|
|
414
|
+
return new Date(a.lastCommittedAt).getTime() - new Date(b.lastCommittedAt).getTime();
|
|
415
|
+
});
|
|
416
|
+
return results;
|
|
407
417
|
};
|
|
408
418
|
}
|
|
409
419
|
|
|
@@ -721,7 +731,7 @@ async function loadSettings(cwd, fileSystem, runtime) {
|
|
|
721
731
|
}
|
|
722
732
|
|
|
723
733
|
// lib/version.ts
|
|
724
|
-
var DUST_VERSION = "0.1.
|
|
734
|
+
var DUST_VERSION = "0.1.112";
|
|
725
735
|
|
|
726
736
|
// lib/cli/middleware.ts
|
|
727
737
|
function applyMiddleware(middlewares, execute) {
|
|
@@ -6098,6 +6108,43 @@ function extractFirstSentence2(paragraph) {
|
|
|
6098
6108
|
return match ? match[1] : null;
|
|
6099
6109
|
}
|
|
6100
6110
|
|
|
6111
|
+
// lib/execution-order.ts
|
|
6112
|
+
function computeExecutionOrder(nodes) {
|
|
6113
|
+
if (nodes.length === 0)
|
|
6114
|
+
return [];
|
|
6115
|
+
const sorted = [...nodes].toSorted((a, b) => {
|
|
6116
|
+
if (a.lastCommittedAt === null && b.lastCommittedAt === null)
|
|
6117
|
+
return 0;
|
|
6118
|
+
if (a.lastCommittedAt === null)
|
|
6119
|
+
return 1;
|
|
6120
|
+
if (b.lastCommittedAt === null)
|
|
6121
|
+
return -1;
|
|
6122
|
+
return new Date(a.lastCommittedAt).getTime() - new Date(b.lastCommittedAt).getTime();
|
|
6123
|
+
});
|
|
6124
|
+
const result = [];
|
|
6125
|
+
const completed = new Set;
|
|
6126
|
+
const nodeMap = new Map(nodes.map((n) => [n.slug, n]));
|
|
6127
|
+
while (result.length < nodes.length) {
|
|
6128
|
+
const next = sorted.find((node) => {
|
|
6129
|
+
if (completed.has(node.slug))
|
|
6130
|
+
return false;
|
|
6131
|
+
return node.blockedBy.every((slug) => completed.has(slug) || !nodeMap.has(slug));
|
|
6132
|
+
});
|
|
6133
|
+
if (!next) {
|
|
6134
|
+
for (const node of sorted) {
|
|
6135
|
+
if (!completed.has(node.slug)) {
|
|
6136
|
+
result.push({ node, executionOrder: result.length + 1 });
|
|
6137
|
+
completed.add(node.slug);
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
break;
|
|
6141
|
+
}
|
|
6142
|
+
result.push({ node: next, executionOrder: result.length + 1 });
|
|
6143
|
+
completed.add(next.slug);
|
|
6144
|
+
}
|
|
6145
|
+
return result;
|
|
6146
|
+
}
|
|
6147
|
+
|
|
6101
6148
|
// lib/artifacts/workflow-tasks.ts
|
|
6102
6149
|
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
6103
6150
|
var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
|
|
@@ -6246,6 +6293,55 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
|
|
|
6246
6293
|
}
|
|
6247
6294
|
return { captureIdeaTasks, workflowTasksByIdeaSlug };
|
|
6248
6295
|
}
|
|
6296
|
+
async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
|
|
6297
|
+
const ideaPath = `${dustPath}/ideas/${ideaSlug}.md`;
|
|
6298
|
+
if (!fileSystem.exists(ideaPath)) {
|
|
6299
|
+
throw new Error(`Idea not found: "${ideaSlug}" (expected file at ${ideaPath})`);
|
|
6300
|
+
}
|
|
6301
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
6302
|
+
if (!fileSystem.exists(tasksPath)) {
|
|
6303
|
+
return null;
|
|
6304
|
+
}
|
|
6305
|
+
const files = await fileSystem.readdir(tasksPath);
|
|
6306
|
+
for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
|
|
6307
|
+
const content = await fileSystem.readFile(`${tasksPath}/${file}`);
|
|
6308
|
+
const taskSlug = file.replace(/\.md$/, "");
|
|
6309
|
+
const match = findWorkflowMatch(content, ideaSlug, taskSlug);
|
|
6310
|
+
if (match) {
|
|
6311
|
+
return match;
|
|
6312
|
+
}
|
|
6313
|
+
}
|
|
6314
|
+
return null;
|
|
6315
|
+
}
|
|
6316
|
+
function findWorkflowMatch(content, ideaSlug, taskSlug) {
|
|
6317
|
+
const taskType = parseTaskType(content);
|
|
6318
|
+
if (taskType) {
|
|
6319
|
+
const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
|
|
6320
|
+
if (heading) {
|
|
6321
|
+
const linkedSlug = extractIdeaSlugFromSection(content, heading);
|
|
6322
|
+
if (linkedSlug === ideaSlug) {
|
|
6323
|
+
return {
|
|
6324
|
+
type: taskType,
|
|
6325
|
+
ideaSlug,
|
|
6326
|
+
taskSlug,
|
|
6327
|
+
resolvedQuestions: parseResolvedQuestions(content)
|
|
6328
|
+
};
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
}
|
|
6332
|
+
for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
|
|
6333
|
+
const linkedSlug = extractIdeaSlugFromSection(content, heading);
|
|
6334
|
+
if (linkedSlug === ideaSlug) {
|
|
6335
|
+
return {
|
|
6336
|
+
type,
|
|
6337
|
+
ideaSlug,
|
|
6338
|
+
taskSlug,
|
|
6339
|
+
resolvedQuestions: parseResolvedQuestions(content)
|
|
6340
|
+
};
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
return null;
|
|
6344
|
+
}
|
|
6249
6345
|
function parseResolvedQuestions(content) {
|
|
6250
6346
|
const lines = content.split(`
|
|
6251
6347
|
`);
|
|
@@ -6286,6 +6382,42 @@ function parseResolvedQuestions(content) {
|
|
|
6286
6382
|
}
|
|
6287
6383
|
return results;
|
|
6288
6384
|
}
|
|
6385
|
+
async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
|
|
6386
|
+
const filePath = `${dustPath}/tasks/${taskSlug}.md`;
|
|
6387
|
+
if (!fileSystem.exists(filePath)) {
|
|
6388
|
+
return null;
|
|
6389
|
+
}
|
|
6390
|
+
const content = await fileSystem.readFile(filePath);
|
|
6391
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
6392
|
+
if (!titleMatch) {
|
|
6393
|
+
return null;
|
|
6394
|
+
}
|
|
6395
|
+
const title = titleMatch[1].trim();
|
|
6396
|
+
const taskType = parseTaskType(content);
|
|
6397
|
+
let ideaTitle;
|
|
6398
|
+
let expedite;
|
|
6399
|
+
if (taskType === "implement") {
|
|
6400
|
+
expedite = true;
|
|
6401
|
+
ideaTitle = title.startsWith(EXPEDITE_IDEA_PREFIX) ? title.slice(EXPEDITE_IDEA_PREFIX.length) : title;
|
|
6402
|
+
} else if (taskType === "capture") {
|
|
6403
|
+
expedite = false;
|
|
6404
|
+
ideaTitle = title.startsWith(CAPTURE_IDEA_PREFIX) ? title.slice(CAPTURE_IDEA_PREFIX.length) : title;
|
|
6405
|
+
} else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
|
|
6406
|
+
ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
|
|
6407
|
+
expedite = true;
|
|
6408
|
+
} else if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
|
|
6409
|
+
ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
|
|
6410
|
+
expedite = false;
|
|
6411
|
+
} else {
|
|
6412
|
+
return null;
|
|
6413
|
+
}
|
|
6414
|
+
const descriptionMatch = content.match(/^## Idea Description\n\n([\s\S]*?)\n\n## /m);
|
|
6415
|
+
if (!descriptionMatch) {
|
|
6416
|
+
return null;
|
|
6417
|
+
}
|
|
6418
|
+
const ideaDescription = descriptionMatch[1];
|
|
6419
|
+
return { ideaTitle, ideaDescription, expedite };
|
|
6420
|
+
}
|
|
6289
6421
|
|
|
6290
6422
|
// lib/lint/validators/content-validator.ts
|
|
6291
6423
|
var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
|
|
@@ -6390,20 +6522,22 @@ function validateTaskType(artifact) {
|
|
|
6390
6522
|
function hasRequiredHeadings(content) {
|
|
6391
6523
|
return /^## Blocked By\s*$/m.test(content) && /^## Definition of Done\s*$/m.test(content);
|
|
6392
6524
|
}
|
|
6393
|
-
function
|
|
6525
|
+
function extractBlockedBySlugs(content) {
|
|
6394
6526
|
const blockedByMatch = content.match(/^## Blocked By\s*\n([\s\S]*?)(?=\n## |\n*$)/m);
|
|
6395
6527
|
const section = blockedByMatch[1].trim();
|
|
6396
6528
|
if (section === "(none)") {
|
|
6397
6529
|
return [];
|
|
6398
6530
|
}
|
|
6399
6531
|
const linkPattern = /\[.*?\]\(([^)]+\.md)\)/g;
|
|
6400
|
-
const
|
|
6532
|
+
const slugs = [];
|
|
6401
6533
|
let match = linkPattern.exec(section);
|
|
6402
6534
|
while (match !== null) {
|
|
6403
|
-
|
|
6535
|
+
const slugMatch = match[1].match(/([^/]+)\.md$/);
|
|
6536
|
+
if (slugMatch)
|
|
6537
|
+
slugs.push(slugMatch[1]);
|
|
6404
6538
|
match = linkPattern.exec(section);
|
|
6405
6539
|
}
|
|
6406
|
-
return
|
|
6540
|
+
return slugs;
|
|
6407
6541
|
}
|
|
6408
6542
|
async function findUnblockedTasks(cwd, fileSystem, directoryFileSorter) {
|
|
6409
6543
|
const dustPath = `${cwd}/.dust`;
|
|
@@ -6415,19 +6549,20 @@ async function findUnblockedTasks(cwd, fileSystem, directoryFileSorter) {
|
|
|
6415
6549
|
return { tasks: [], invalidTasks: [] };
|
|
6416
6550
|
}
|
|
6417
6551
|
const files = await fileSystem.readdir(tasksPath);
|
|
6418
|
-
|
|
6419
|
-
if (directoryFileSorter) {
|
|
6420
|
-
mdFiles = await directoryFileSorter(tasksPath, mdFiles);
|
|
6421
|
-
} else {
|
|
6422
|
-
mdFiles.sort((a, b) => {
|
|
6423
|
-
const aTime = fileSystem.getFileCreationTime(`${tasksPath}/${a}`);
|
|
6424
|
-
const bTime = fileSystem.getFileCreationTime(`${tasksPath}/${b}`);
|
|
6425
|
-
return aTime - bTime;
|
|
6426
|
-
});
|
|
6427
|
-
}
|
|
6552
|
+
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
6428
6553
|
if (mdFiles.length === 0) {
|
|
6429
6554
|
return { tasks: [], invalidTasks: [] };
|
|
6430
6555
|
}
|
|
6556
|
+
let timestamps;
|
|
6557
|
+
if (directoryFileSorter) {
|
|
6558
|
+
const results = await directoryFileSorter(tasksPath, mdFiles);
|
|
6559
|
+
timestamps = new Map(results.map((r) => [r.file, r.lastCommittedAt]));
|
|
6560
|
+
} else {
|
|
6561
|
+
timestamps = new Map(mdFiles.map((f) => {
|
|
6562
|
+
const ms = fileSystem.getFileCreationTime(`${tasksPath}/${f}`);
|
|
6563
|
+
return [f, ms > 0 ? new Date(ms).toISOString() : null];
|
|
6564
|
+
}));
|
|
6565
|
+
}
|
|
6431
6566
|
const taskFiles = [];
|
|
6432
6567
|
for (const file of mdFiles) {
|
|
6433
6568
|
const filePath = `${tasksPath}/${file}`;
|
|
@@ -6448,16 +6583,22 @@ async function findUnblockedTasks(cwd, fileSystem, directoryFileSorter) {
|
|
|
6448
6583
|
});
|
|
6449
6584
|
}
|
|
6450
6585
|
}
|
|
6451
|
-
const
|
|
6586
|
+
const taskNodes = validTaskFiles.map(({ file, content }) => ({
|
|
6587
|
+
slug: file.replace(/\.md$/, ""),
|
|
6588
|
+
file,
|
|
6589
|
+
content,
|
|
6590
|
+
blockedBy: extractBlockedBySlugs(content),
|
|
6591
|
+
lastCommittedAt: timestamps.get(file) ?? null
|
|
6592
|
+
}));
|
|
6593
|
+
const ordered = computeExecutionOrder(taskNodes);
|
|
6594
|
+
const existingSlugs = new Set(taskNodes.map((t) => t.slug));
|
|
6452
6595
|
const tasks = [];
|
|
6453
|
-
for (const {
|
|
6454
|
-
const
|
|
6455
|
-
const hasIncompleteBlocker = blockers.some((blocker) => existingTasks.has(blocker));
|
|
6596
|
+
for (const { node } of ordered) {
|
|
6597
|
+
const hasIncompleteBlocker = node.blockedBy.some((slug) => existingSlugs.has(slug));
|
|
6456
6598
|
if (!hasIncompleteBlocker) {
|
|
6457
|
-
const title = extractTitle(content);
|
|
6458
|
-
const openingSentence = extractOpeningSentence(content);
|
|
6459
|
-
|
|
6460
|
-
tasks.push({ path: relativePath, title, openingSentence });
|
|
6599
|
+
const title = extractTitle(node.content);
|
|
6600
|
+
const openingSentence = extractOpeningSentence(node.content);
|
|
6601
|
+
tasks.push({ path: `.dust/tasks/${node.file}`, title, openingSentence });
|
|
6461
6602
|
}
|
|
6462
6603
|
}
|
|
6463
6604
|
return { tasks, invalidTasks };
|
|
@@ -9426,6 +9567,34 @@ function executeMessageEffects(effects, dependencies) {
|
|
|
9426
9567
|
}
|
|
9427
9568
|
}
|
|
9428
9569
|
|
|
9570
|
+
// lib/bucket/bucket-dependencies.ts
|
|
9571
|
+
function createAuthFileSystem(dependencies) {
|
|
9572
|
+
return {
|
|
9573
|
+
exists: (path3) => {
|
|
9574
|
+
try {
|
|
9575
|
+
dependencies.accessSync(path3);
|
|
9576
|
+
return true;
|
|
9577
|
+
} catch {
|
|
9578
|
+
return false;
|
|
9579
|
+
}
|
|
9580
|
+
},
|
|
9581
|
+
isDirectory: (path3) => {
|
|
9582
|
+
try {
|
|
9583
|
+
return dependencies.statSync(path3).isDirectory();
|
|
9584
|
+
} catch {
|
|
9585
|
+
return false;
|
|
9586
|
+
}
|
|
9587
|
+
},
|
|
9588
|
+
getFileCreationTime: (path3) => dependencies.statSync(path3).birthtimeMs,
|
|
9589
|
+
readFile: (path3) => dependencies.readFile(path3, "utf8"),
|
|
9590
|
+
writeFile: (path3, content) => dependencies.writeFile(path3, content, "utf8"),
|
|
9591
|
+
mkdir: (path3, options) => dependencies.mkdir(path3, options).then(() => {}),
|
|
9592
|
+
readdir: dependencies.readdir.bind(dependencies),
|
|
9593
|
+
chmod: dependencies.chmod.bind(dependencies),
|
|
9594
|
+
rename: dependencies.rename.bind(dependencies)
|
|
9595
|
+
};
|
|
9596
|
+
}
|
|
9597
|
+
|
|
9429
9598
|
// lib/bucket/native-io.ts
|
|
9430
9599
|
import { spawn as nodeSpawn4 } from "node:child_process";
|
|
9431
9600
|
import { EventEmitter } from "node:events";
|
|
@@ -9897,32 +10066,6 @@ function findRepoPathByRepositoryId(repositories, repositoryId) {
|
|
|
9897
10066
|
return;
|
|
9898
10067
|
}
|
|
9899
10068
|
var DEFAULT_DUSTBUCKET_WS_URL = "wss://dustbucket.com/agent/connect";
|
|
9900
|
-
function createAuthFileSystem(dependencies) {
|
|
9901
|
-
return {
|
|
9902
|
-
exists: (path3) => {
|
|
9903
|
-
try {
|
|
9904
|
-
dependencies.accessSync(path3);
|
|
9905
|
-
return true;
|
|
9906
|
-
} catch {
|
|
9907
|
-
return false;
|
|
9908
|
-
}
|
|
9909
|
-
},
|
|
9910
|
-
isDirectory: (path3) => {
|
|
9911
|
-
try {
|
|
9912
|
-
return dependencies.statSync(path3).isDirectory();
|
|
9913
|
-
} catch {
|
|
9914
|
-
return false;
|
|
9915
|
-
}
|
|
9916
|
-
},
|
|
9917
|
-
getFileCreationTime: (path3) => dependencies.statSync(path3).birthtimeMs,
|
|
9918
|
-
readFile: (path3) => dependencies.readFile(path3, "utf8"),
|
|
9919
|
-
writeFile: (path3, content) => dependencies.writeFile(path3, content, "utf8"),
|
|
9920
|
-
mkdir: (path3, options) => dependencies.mkdir(path3, options).then(() => {}),
|
|
9921
|
-
readdir: dependencies.readdir.bind(dependencies),
|
|
9922
|
-
chmod: dependencies.chmod.bind(dependencies),
|
|
9923
|
-
rename: dependencies.rename.bind(dependencies)
|
|
9924
|
-
};
|
|
9925
|
-
}
|
|
9926
10069
|
function createInitialState() {
|
|
9927
10070
|
const sessionId = crypto.randomUUID();
|
|
9928
10071
|
const systemBuffer = createLogBuffer();
|
|
@@ -10732,122 +10875,473 @@ Run \`dust bucket tool ${toolName}\` to see available operations.`);
|
|
|
10732
10875
|
// lib/cli/commands/lint-markdown.ts
|
|
10733
10876
|
import { isAbsolute, join as join11, relative, sep } from "node:path";
|
|
10734
10877
|
|
|
10735
|
-
// lib/artifacts/
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10878
|
+
// lib/artifacts/facts.ts
|
|
10879
|
+
async function parseFact(fileSystem, dustPath, slug) {
|
|
10880
|
+
const factPath = `${dustPath}/facts/${slug}.md`;
|
|
10881
|
+
if (!fileSystem.exists(factPath)) {
|
|
10882
|
+
throw new Error(`Fact not found: "${slug}" (expected file at ${factPath})`);
|
|
10883
|
+
}
|
|
10884
|
+
const content = await fileSystem.readFile(factPath);
|
|
10885
|
+
const title = extractTitle(content);
|
|
10886
|
+
if (!title) {
|
|
10887
|
+
throw new Error(`Fact file has no title: ${factPath}`);
|
|
10888
|
+
}
|
|
10889
|
+
return {
|
|
10890
|
+
slug,
|
|
10891
|
+
title,
|
|
10892
|
+
content
|
|
10893
|
+
};
|
|
10894
|
+
}
|
|
10742
10895
|
|
|
10743
|
-
// lib/
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
let
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
10896
|
+
// lib/artifacts/ideas.ts
|
|
10897
|
+
function parseOpenQuestions(content) {
|
|
10898
|
+
const lines = content.split(`
|
|
10899
|
+
`);
|
|
10900
|
+
const questions = [];
|
|
10901
|
+
let inOpenQuestions = false;
|
|
10902
|
+
let currentQuestion = null;
|
|
10903
|
+
let currentOption = null;
|
|
10904
|
+
let descriptionLines = [];
|
|
10905
|
+
function flushOption() {
|
|
10906
|
+
if (currentOption) {
|
|
10907
|
+
currentOption.description = descriptionLines.join(`
|
|
10908
|
+
`).trim();
|
|
10909
|
+
descriptionLines = [];
|
|
10910
|
+
currentOption = null;
|
|
10756
10911
|
}
|
|
10757
|
-
throw error;
|
|
10758
10912
|
}
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
if (
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
message: `Hidden file "${entry}" found in content directory`
|
|
10765
|
-
});
|
|
10766
|
-
continue;
|
|
10913
|
+
function flushQuestion() {
|
|
10914
|
+
flushOption();
|
|
10915
|
+
if (currentQuestion) {
|
|
10916
|
+
questions.push(currentQuestion);
|
|
10917
|
+
currentQuestion = null;
|
|
10767
10918
|
}
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10919
|
+
}
|
|
10920
|
+
let inCodeFence = false;
|
|
10921
|
+
for (const line of lines) {
|
|
10922
|
+
if (line.startsWith("```")) {
|
|
10923
|
+
inCodeFence = !inCodeFence;
|
|
10924
|
+
if (currentOption) {
|
|
10925
|
+
descriptionLines.push(line);
|
|
10926
|
+
}
|
|
10773
10927
|
continue;
|
|
10774
10928
|
}
|
|
10775
|
-
if (
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10929
|
+
if (inCodeFence) {
|
|
10930
|
+
if (currentOption) {
|
|
10931
|
+
descriptionLines.push(line);
|
|
10932
|
+
}
|
|
10933
|
+
continue;
|
|
10780
10934
|
}
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
try {
|
|
10788
|
-
entries = await fileSystem.readdir(dustPath);
|
|
10789
|
-
} catch (error) {
|
|
10790
|
-
if (isErrorCode(error, "ENOENT")) {
|
|
10791
|
-
return [];
|
|
10935
|
+
if (line.startsWith("## ")) {
|
|
10936
|
+
if (inOpenQuestions) {
|
|
10937
|
+
flushQuestion();
|
|
10938
|
+
}
|
|
10939
|
+
inOpenQuestions = line.trimEnd() === "## Open Questions";
|
|
10940
|
+
continue;
|
|
10792
10941
|
}
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
for (const entry of entries) {
|
|
10802
|
-
const entryPath = `${dustPath}/${entry}`;
|
|
10803
|
-
if (entry === "Dockerfile") {
|
|
10804
|
-
violations.push({
|
|
10805
|
-
file: entryPath,
|
|
10806
|
-
message: '".dust/Dockerfile" is no longer supported. Move it to ".dust/config/container/Dockerfile".'
|
|
10807
|
-
});
|
|
10942
|
+
if (!inOpenQuestions)
|
|
10943
|
+
continue;
|
|
10944
|
+
if (line.startsWith("### ")) {
|
|
10945
|
+
flushQuestion();
|
|
10946
|
+
currentQuestion = {
|
|
10947
|
+
question: line.slice(4).trim(),
|
|
10948
|
+
options: []
|
|
10949
|
+
};
|
|
10808
10950
|
continue;
|
|
10809
10951
|
}
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10952
|
+
if (line.startsWith("#### ")) {
|
|
10953
|
+
flushOption();
|
|
10954
|
+
currentOption = {
|
|
10955
|
+
name: line.slice(5).trim(),
|
|
10956
|
+
description: ""
|
|
10957
|
+
};
|
|
10958
|
+
if (currentQuestion) {
|
|
10959
|
+
currentQuestion.options.push(currentOption);
|
|
10814
10960
|
}
|
|
10815
|
-
violations.push({
|
|
10816
|
-
file: entryPath,
|
|
10817
|
-
message: `Unexpected file "${entry}" in .dust/. Allowed root paths: ${allowedRootList}`
|
|
10818
|
-
});
|
|
10819
10961
|
continue;
|
|
10820
10962
|
}
|
|
10821
|
-
if (
|
|
10822
|
-
|
|
10823
|
-
file: entryPath,
|
|
10824
|
-
message: `Unexpected directory "${entry}" in .dust/. Allowed root paths: ${allowedRootList}`
|
|
10825
|
-
});
|
|
10963
|
+
if (currentOption) {
|
|
10964
|
+
descriptionLines.push(line);
|
|
10826
10965
|
}
|
|
10827
10966
|
}
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10967
|
+
flushQuestion();
|
|
10968
|
+
return questions;
|
|
10969
|
+
}
|
|
10970
|
+
async function parseIdea(fileSystem, dustPath, slug) {
|
|
10971
|
+
const ideaPath = `${dustPath}/ideas/${slug}.md`;
|
|
10972
|
+
if (!fileSystem.exists(ideaPath)) {
|
|
10973
|
+
throw new Error(`Idea not found: "${slug}" (expected file at ${ideaPath})`);
|
|
10831
10974
|
}
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
if (isErrorCode(error, "ENOENT")) {
|
|
10837
|
-
return violations;
|
|
10838
|
-
}
|
|
10839
|
-
throw error;
|
|
10975
|
+
const content = await fileSystem.readFile(ideaPath);
|
|
10976
|
+
const title = extractTitle(content);
|
|
10977
|
+
if (!title) {
|
|
10978
|
+
throw new Error(`Idea file has no title: ${ideaPath}`);
|
|
10840
10979
|
}
|
|
10841
|
-
const
|
|
10842
|
-
const
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10980
|
+
const openingSentence = extractOpeningSentence(content);
|
|
10981
|
+
const openQuestions = parseOpenQuestions(content);
|
|
10982
|
+
return {
|
|
10983
|
+
slug,
|
|
10984
|
+
title,
|
|
10985
|
+
openingSentence,
|
|
10986
|
+
content,
|
|
10987
|
+
openQuestions
|
|
10988
|
+
};
|
|
10989
|
+
}
|
|
10990
|
+
|
|
10991
|
+
// lib/artifacts/principles.ts
|
|
10992
|
+
function extractLinksFromSection(content, sectionHeading) {
|
|
10993
|
+
const lines = content.split(`
|
|
10994
|
+
`);
|
|
10995
|
+
const links = [];
|
|
10996
|
+
let inSection = false;
|
|
10997
|
+
for (const line of lines) {
|
|
10998
|
+
if (line.startsWith("## ")) {
|
|
10999
|
+
inSection = line.trimEnd() === `## ${sectionHeading}`;
|
|
11000
|
+
continue;
|
|
11001
|
+
}
|
|
11002
|
+
if (!inSection)
|
|
11003
|
+
continue;
|
|
11004
|
+
if (line.startsWith("# "))
|
|
11005
|
+
break;
|
|
11006
|
+
const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
|
|
11007
|
+
if (linkMatch) {
|
|
11008
|
+
const target = linkMatch[2];
|
|
11009
|
+
const slugMatch = target.match(/([^/]+)\.md$/);
|
|
11010
|
+
if (slugMatch) {
|
|
11011
|
+
links.push(slugMatch[1]);
|
|
11012
|
+
}
|
|
11013
|
+
}
|
|
11014
|
+
}
|
|
11015
|
+
return links;
|
|
11016
|
+
}
|
|
11017
|
+
function extractSingleLinkFromSection(content, sectionHeading) {
|
|
11018
|
+
const links = extractLinksFromSection(content, sectionHeading);
|
|
11019
|
+
return links.length === 1 ? links[0] : null;
|
|
11020
|
+
}
|
|
11021
|
+
async function parsePrinciple(fileSystem, dustPath, slug) {
|
|
11022
|
+
const principlePath = `${dustPath}/principles/${slug}.md`;
|
|
11023
|
+
if (!fileSystem.exists(principlePath)) {
|
|
11024
|
+
throw new Error(`Principle not found: "${slug}" (expected file at ${principlePath})`);
|
|
11025
|
+
}
|
|
11026
|
+
const content = await fileSystem.readFile(principlePath);
|
|
11027
|
+
const title = extractTitle(content) || slug;
|
|
11028
|
+
const parentPrinciple = extractSingleLinkFromSection(content, "Parent Principle");
|
|
11029
|
+
const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
|
|
11030
|
+
return {
|
|
11031
|
+
slug,
|
|
11032
|
+
title,
|
|
11033
|
+
content,
|
|
11034
|
+
parentPrinciple,
|
|
11035
|
+
subPrinciples
|
|
11036
|
+
};
|
|
11037
|
+
}
|
|
11038
|
+
|
|
11039
|
+
// lib/artifacts/tasks.ts
|
|
11040
|
+
function extractLinksFromSection2(content, sectionHeading) {
|
|
11041
|
+
const lines = content.split(`
|
|
11042
|
+
`);
|
|
11043
|
+
const links = [];
|
|
11044
|
+
let inSection = false;
|
|
11045
|
+
for (const line of lines) {
|
|
11046
|
+
if (line.startsWith("## ")) {
|
|
11047
|
+
inSection = line.trimEnd() === `## ${sectionHeading}`;
|
|
11048
|
+
continue;
|
|
11049
|
+
}
|
|
11050
|
+
if (!inSection)
|
|
11051
|
+
continue;
|
|
11052
|
+
if (line.startsWith("# "))
|
|
11053
|
+
break;
|
|
11054
|
+
const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
|
|
11055
|
+
if (linkMatch) {
|
|
11056
|
+
const target = linkMatch[2];
|
|
11057
|
+
const slugMatch = target.match(/([^/]+)\.md$/);
|
|
11058
|
+
if (slugMatch) {
|
|
11059
|
+
links.push(slugMatch[1]);
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
}
|
|
11063
|
+
return links;
|
|
11064
|
+
}
|
|
11065
|
+
function extractDefinitionOfDone(content) {
|
|
11066
|
+
const lines = content.split(`
|
|
11067
|
+
`);
|
|
11068
|
+
const items = [];
|
|
11069
|
+
let inSection = false;
|
|
11070
|
+
for (const line of lines) {
|
|
11071
|
+
if (line.startsWith("## ")) {
|
|
11072
|
+
inSection = line.trimEnd() === "## Definition of Done";
|
|
11073
|
+
continue;
|
|
11074
|
+
}
|
|
11075
|
+
if (!inSection)
|
|
11076
|
+
continue;
|
|
11077
|
+
if (line.startsWith("# "))
|
|
11078
|
+
break;
|
|
11079
|
+
const listMatch = line.match(/^-\s+(.+)$/);
|
|
11080
|
+
if (listMatch) {
|
|
11081
|
+
items.push(listMatch[1].trim());
|
|
11082
|
+
}
|
|
11083
|
+
}
|
|
11084
|
+
return items;
|
|
11085
|
+
}
|
|
11086
|
+
async function parseTask(fileSystem, dustPath, slug) {
|
|
11087
|
+
const taskPath = `${dustPath}/tasks/${slug}.md`;
|
|
11088
|
+
if (!fileSystem.exists(taskPath)) {
|
|
11089
|
+
throw new Error(`Task not found: "${slug}" (expected file at ${taskPath})`);
|
|
11090
|
+
}
|
|
11091
|
+
const content = await fileSystem.readFile(taskPath);
|
|
11092
|
+
const title = extractTitle(content);
|
|
11093
|
+
if (!title) {
|
|
11094
|
+
throw new Error(`Task file has no title: ${taskPath}`);
|
|
11095
|
+
}
|
|
11096
|
+
const principles = extractLinksFromSection2(content, "Principles");
|
|
11097
|
+
const blockedBy = extractLinksFromSection2(content, "Blocked By");
|
|
11098
|
+
const definitionOfDone = extractDefinitionOfDone(content);
|
|
11099
|
+
return {
|
|
11100
|
+
slug,
|
|
11101
|
+
title,
|
|
11102
|
+
content,
|
|
11103
|
+
principles,
|
|
11104
|
+
blockedBy,
|
|
11105
|
+
definitionOfDone
|
|
11106
|
+
};
|
|
11107
|
+
}
|
|
11108
|
+
|
|
11109
|
+
// lib/artifacts/repository-principle-hierarchy.ts
|
|
11110
|
+
function sortNodes(nodes) {
|
|
11111
|
+
nodes.sort((a, b) => a.title.localeCompare(b.title));
|
|
11112
|
+
for (const node of nodes) {
|
|
11113
|
+
sortNodes(node.children);
|
|
11114
|
+
}
|
|
11115
|
+
}
|
|
11116
|
+
async function getRepositoryPrincipleHierarchy(repository) {
|
|
11117
|
+
const slugs = await repository.listPrinciples();
|
|
11118
|
+
if (slugs.length === 0) {
|
|
11119
|
+
return [];
|
|
11120
|
+
}
|
|
11121
|
+
const principles = await Promise.all(slugs.map((slug) => repository.parsePrinciple({ slug })));
|
|
11122
|
+
const principleSet = new Set(slugs);
|
|
11123
|
+
const nodeBySlug = new Map;
|
|
11124
|
+
for (const p of principles) {
|
|
11125
|
+
nodeBySlug.set(p.slug, {
|
|
11126
|
+
slug: p.slug,
|
|
11127
|
+
title: p.title,
|
|
11128
|
+
children: []
|
|
11129
|
+
});
|
|
11130
|
+
}
|
|
11131
|
+
const roots = [];
|
|
11132
|
+
for (const p of principles) {
|
|
11133
|
+
const node = nodeBySlug.get(p.slug);
|
|
11134
|
+
const parentSlug = p.parentPrinciple;
|
|
11135
|
+
if (!parentSlug || !principleSet.has(parentSlug)) {
|
|
11136
|
+
roots.push(node);
|
|
11137
|
+
} else {
|
|
11138
|
+
nodeBySlug.get(parentSlug).children.push(node);
|
|
11139
|
+
}
|
|
11140
|
+
}
|
|
11141
|
+
sortNodes(roots);
|
|
11142
|
+
return roots;
|
|
11143
|
+
}
|
|
11144
|
+
|
|
11145
|
+
// lib/artifacts/index.ts
|
|
11146
|
+
var ARTIFACT_TYPES = [
|
|
11147
|
+
"facts",
|
|
11148
|
+
"ideas",
|
|
11149
|
+
"principles",
|
|
11150
|
+
"tasks"
|
|
11151
|
+
];
|
|
11152
|
+
function buildReadOperations(fileSystem, dustPath) {
|
|
11153
|
+
return {
|
|
11154
|
+
artifactPath(type, slug) {
|
|
11155
|
+
return `${dustPath}/${type}/${slug}.md`;
|
|
11156
|
+
},
|
|
11157
|
+
async parseIdea(options) {
|
|
11158
|
+
return parseIdea(fileSystem, dustPath, options.slug);
|
|
11159
|
+
},
|
|
11160
|
+
async listIdeas() {
|
|
11161
|
+
const ideasPath = `${dustPath}/ideas`;
|
|
11162
|
+
if (!fileSystem.exists(ideasPath)) {
|
|
11163
|
+
return [];
|
|
11164
|
+
}
|
|
11165
|
+
const files = await fileSystem.readdir(ideasPath);
|
|
11166
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
|
|
11167
|
+
},
|
|
11168
|
+
async parsePrinciple(options) {
|
|
11169
|
+
return parsePrinciple(fileSystem, dustPath, options.slug);
|
|
11170
|
+
},
|
|
11171
|
+
async listPrinciples() {
|
|
11172
|
+
const principlesPath = `${dustPath}/principles`;
|
|
11173
|
+
if (!fileSystem.exists(principlesPath)) {
|
|
11174
|
+
return [];
|
|
11175
|
+
}
|
|
11176
|
+
const files = await fileSystem.readdir(principlesPath);
|
|
11177
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
|
|
11178
|
+
},
|
|
11179
|
+
async parseFact(options) {
|
|
11180
|
+
return parseFact(fileSystem, dustPath, options.slug);
|
|
11181
|
+
},
|
|
11182
|
+
async listFacts() {
|
|
11183
|
+
const factsPath = `${dustPath}/facts`;
|
|
11184
|
+
if (!fileSystem.exists(factsPath)) {
|
|
11185
|
+
return [];
|
|
11186
|
+
}
|
|
11187
|
+
const files = await fileSystem.readdir(factsPath);
|
|
11188
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
|
|
11189
|
+
},
|
|
11190
|
+
async parseTask(options) {
|
|
11191
|
+
return parseTask(fileSystem, dustPath, options.slug);
|
|
11192
|
+
},
|
|
11193
|
+
async listTasks() {
|
|
11194
|
+
const tasksPath = `${dustPath}/tasks`;
|
|
11195
|
+
if (!fileSystem.exists(tasksPath)) {
|
|
11196
|
+
return [];
|
|
11197
|
+
}
|
|
11198
|
+
const files = await fileSystem.readdir(tasksPath);
|
|
11199
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
|
|
11200
|
+
},
|
|
11201
|
+
async findWorkflowTaskForIdea(options) {
|
|
11202
|
+
return findWorkflowTaskForIdea(fileSystem, dustPath, options.ideaSlug);
|
|
11203
|
+
},
|
|
11204
|
+
async parseCaptureIdeaTask(options) {
|
|
11205
|
+
return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
|
|
11206
|
+
},
|
|
11207
|
+
async buildTaskGraph() {
|
|
11208
|
+
const taskSlugs = await this.listTasks();
|
|
11209
|
+
const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
|
|
11210
|
+
const workflowTypeByTaskSlug = new Map;
|
|
11211
|
+
for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
|
|
11212
|
+
workflowTypeByTaskSlug.set(match.taskSlug, match.type);
|
|
11213
|
+
}
|
|
11214
|
+
const nodes = [];
|
|
11215
|
+
const edges = [];
|
|
11216
|
+
for (const slug of taskSlugs) {
|
|
11217
|
+
const task = await this.parseTask({ slug });
|
|
11218
|
+
nodes.push({
|
|
11219
|
+
task,
|
|
11220
|
+
workflowType: workflowTypeByTaskSlug.get(slug) ?? null
|
|
11221
|
+
});
|
|
11222
|
+
for (const blockerSlug of task.blockedBy) {
|
|
11223
|
+
edges.push({ from: blockerSlug, to: slug });
|
|
11224
|
+
}
|
|
11225
|
+
}
|
|
11226
|
+
return { nodes, edges };
|
|
11227
|
+
},
|
|
11228
|
+
async getRepositoryPrincipleHierarchy() {
|
|
11229
|
+
return getRepositoryPrincipleHierarchy(this);
|
|
11230
|
+
}
|
|
11231
|
+
};
|
|
11232
|
+
}
|
|
11233
|
+
function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
|
|
11234
|
+
return buildReadOperations(fileSystem, dustPath);
|
|
11235
|
+
}
|
|
11236
|
+
|
|
11237
|
+
// lib/lint/validators/directory-validator.ts
|
|
11238
|
+
var EXPECTED_DIRECTORIES = [...ARTIFACT_TYPES, "config"];
|
|
11239
|
+
var EXPECTED_ROOT_FILES = ["repository.md"];
|
|
11240
|
+
var EXPECTED_CONFIG_FILES = ["settings.json"];
|
|
11241
|
+
var EXPECTED_CONFIG_DIRECTORIES = ["audits", "agents", "container", "hints"];
|
|
11242
|
+
async function validateContentDirectoryFiles(dirPath, fileSystem) {
|
|
11243
|
+
const violations = [];
|
|
11244
|
+
let entries;
|
|
11245
|
+
try {
|
|
11246
|
+
entries = await fileSystem.readdir(dirPath);
|
|
11247
|
+
} catch (error) {
|
|
11248
|
+
if (isErrorCode(error, "ENOENT")) {
|
|
11249
|
+
return [];
|
|
11250
|
+
}
|
|
11251
|
+
throw error;
|
|
11252
|
+
}
|
|
11253
|
+
for (const entry of entries) {
|
|
11254
|
+
const entryPath = `${dirPath}/${entry}`;
|
|
11255
|
+
if (entry.startsWith(".")) {
|
|
11256
|
+
violations.push({
|
|
11257
|
+
file: entryPath,
|
|
11258
|
+
message: `Hidden file "${entry}" found in content directory`
|
|
11259
|
+
});
|
|
11260
|
+
continue;
|
|
11261
|
+
}
|
|
11262
|
+
if (fileSystem.isDirectory(entryPath)) {
|
|
11263
|
+
violations.push({
|
|
11264
|
+
file: entryPath,
|
|
11265
|
+
message: `Subdirectory "${entry}" found in content directory (content directories should be flat)`
|
|
11266
|
+
});
|
|
11267
|
+
continue;
|
|
11268
|
+
}
|
|
11269
|
+
if (!entry.endsWith(".md")) {
|
|
11270
|
+
violations.push({
|
|
11271
|
+
file: entryPath,
|
|
11272
|
+
message: `Non-markdown file "${entry}" found in content directory`
|
|
11273
|
+
});
|
|
11274
|
+
}
|
|
11275
|
+
}
|
|
11276
|
+
return violations;
|
|
11277
|
+
}
|
|
11278
|
+
async function validateDirectoryStructure(dustPath, fileSystem) {
|
|
11279
|
+
const violations = [];
|
|
11280
|
+
let entries;
|
|
11281
|
+
try {
|
|
11282
|
+
entries = await fileSystem.readdir(dustPath);
|
|
11283
|
+
} catch (error) {
|
|
11284
|
+
if (isErrorCode(error, "ENOENT")) {
|
|
11285
|
+
return [];
|
|
11286
|
+
}
|
|
11287
|
+
throw error;
|
|
11288
|
+
}
|
|
11289
|
+
const allowedDirectories = new Set(EXPECTED_DIRECTORIES);
|
|
11290
|
+
const allowedRootFiles = new Set(EXPECTED_ROOT_FILES);
|
|
11291
|
+
const allowedRootList = [
|
|
11292
|
+
...[...allowedDirectories].toSorted().map((directory) => `${directory}/`),
|
|
11293
|
+
...EXPECTED_ROOT_FILES
|
|
11294
|
+
].join(", ");
|
|
11295
|
+
for (const entry of entries) {
|
|
11296
|
+
const entryPath = `${dustPath}/${entry}`;
|
|
11297
|
+
if (entry === "Dockerfile") {
|
|
11298
|
+
violations.push({
|
|
11299
|
+
file: entryPath,
|
|
11300
|
+
message: '".dust/Dockerfile" is no longer supported. Move it to ".dust/config/container/Dockerfile".'
|
|
11301
|
+
});
|
|
11302
|
+
continue;
|
|
11303
|
+
}
|
|
11304
|
+
const isDirectory = fileSystem.isDirectory(entryPath);
|
|
11305
|
+
if (!isDirectory) {
|
|
11306
|
+
if (allowedRootFiles.has(entry)) {
|
|
11307
|
+
continue;
|
|
11308
|
+
}
|
|
11309
|
+
violations.push({
|
|
11310
|
+
file: entryPath,
|
|
11311
|
+
message: `Unexpected file "${entry}" in .dust/. Allowed root paths: ${allowedRootList}`
|
|
11312
|
+
});
|
|
11313
|
+
continue;
|
|
11314
|
+
}
|
|
11315
|
+
if (!allowedDirectories.has(entry)) {
|
|
11316
|
+
violations.push({
|
|
11317
|
+
file: entryPath,
|
|
11318
|
+
message: `Unexpected directory "${entry}" in .dust/. Allowed root paths: ${allowedRootList}`
|
|
11319
|
+
});
|
|
11320
|
+
}
|
|
11321
|
+
}
|
|
11322
|
+
const configPath = `${dustPath}/config`;
|
|
11323
|
+
if (!fileSystem.isDirectory(configPath)) {
|
|
11324
|
+
return violations;
|
|
11325
|
+
}
|
|
11326
|
+
let configEntries;
|
|
11327
|
+
try {
|
|
11328
|
+
configEntries = await fileSystem.readdir(configPath);
|
|
11329
|
+
} catch (error) {
|
|
11330
|
+
if (isErrorCode(error, "ENOENT")) {
|
|
11331
|
+
return violations;
|
|
11332
|
+
}
|
|
11333
|
+
throw error;
|
|
11334
|
+
}
|
|
11335
|
+
const allowedConfigFiles = new Set(EXPECTED_CONFIG_FILES);
|
|
11336
|
+
const allowedConfigDirectories = new Set(EXPECTED_CONFIG_DIRECTORIES);
|
|
11337
|
+
const allowedConfigList = [
|
|
11338
|
+
...EXPECTED_CONFIG_DIRECTORIES.map((directory) => `${directory}/`),
|
|
11339
|
+
...EXPECTED_CONFIG_FILES
|
|
11340
|
+
].toSorted().join(", ");
|
|
11341
|
+
for (const entry of configEntries) {
|
|
11342
|
+
const entryPath = `${configPath}/${entry}`;
|
|
11343
|
+
if (fileSystem.isDirectory(entryPath)) {
|
|
11344
|
+
if (!allowedConfigDirectories.has(entry)) {
|
|
10851
11345
|
violations.push({
|
|
10852
11346
|
file: entryPath,
|
|
10853
11347
|
message: `Unexpected directory "${entry}" in .dust/config/. Allowed entries: ${allowedConfigList}`
|
|
@@ -11720,61 +12214,120 @@ async function check(dependencies, shellRunner, clock, _setInterval, _clearInter
|
|
|
11720
12214
|
return { exitCode };
|
|
11721
12215
|
}
|
|
11722
12216
|
|
|
11723
|
-
// lib/bundled-core-principles.ts
|
|
11724
|
-
var BUNDLED_PRINCIPLES = [
|
|
12217
|
+
// lib/bundled-core-principles.ts
|
|
12218
|
+
var BUNDLED_PRINCIPLES = [
|
|
12219
|
+
{
|
|
12220
|
+
slug: "design-for-testability",
|
|
12221
|
+
content: `# Design for Testability
|
|
12222
|
+
|
|
12223
|
+
Design code to be testable first; good structure follows naturally.
|
|
12224
|
+
|
|
12225
|
+
Testability should be a primary design driver, not a quality to be retrofitted. When code is designed to be testable from the start, it naturally becomes decoupled, explicit in its dependencies, and clear in its interfaces.
|
|
12226
|
+
|
|
12227
|
+
The discipline of testability forces good design: functions become pure, dependencies become explicit, side effects become isolated. Rather than viewing testability as a tax on production code, recognize it as a compass that points toward better architecture.
|
|
12228
|
+
|
|
12229
|
+
This is particularly important in agent-driven development. Agents cannot manually verify their changes—they rely entirely on tests. Code that resists testing resists autonomous modification.
|
|
12230
|
+
|
|
12231
|
+
## Parent Principle
|
|
12232
|
+
|
|
12233
|
+
- [Decoupled Code](decoupled-code.md)
|
|
12234
|
+
|
|
12235
|
+
## Sub-Principles
|
|
12236
|
+
|
|
12237
|
+
- (none)
|
|
12238
|
+
`
|
|
12239
|
+
},
|
|
11725
12240
|
{
|
|
11726
|
-
slug: "
|
|
11727
|
-
content: `#
|
|
12241
|
+
slug: "fast-feedback-loops",
|
|
12242
|
+
content: `# Fast Feedback Loops
|
|
11728
12243
|
|
|
11729
|
-
|
|
12244
|
+
The primary feedback loop — write code, run checks, see results — should be as fast as possible.
|
|
11730
12245
|
|
|
11731
|
-
|
|
12246
|
+
Fast feedback is the foundation of productive development, for both humans and agents. When tests, linters, and type checks run in seconds rather than minutes, developers iterate more frequently and catch problems earlier. Agents especially benefit because they operate in tight loops of change-and-verify; slow feedback wastes tokens and context window space on waiting rather than working.
|
|
11732
12247
|
|
|
11733
|
-
|
|
12248
|
+
Dust should help projects measure the speed of their feedback loops, identify bottlenecks, and keep them fast as the codebase grows. This includes promoting practices like unit tests over integration tests for speed, incremental compilation, and check parallelisation.
|
|
11734
12249
|
|
|
11735
|
-
##
|
|
12250
|
+
## Parent Principle
|
|
11736
12251
|
|
|
11737
|
-
|
|
12252
|
+
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
12253
|
+
|
|
12254
|
+
## Sub-Principles
|
|
12255
|
+
|
|
12256
|
+
- (none)
|
|
12257
|
+
`
|
|
12258
|
+
},
|
|
12259
|
+
{
|
|
12260
|
+
slug: "test-isolation",
|
|
12261
|
+
content: `# Test Isolation
|
|
12262
|
+
|
|
12263
|
+
Tests should not interfere with one another. Each test must be independently runnable and produce the same result regardless of execution order or which other tests run alongside it.
|
|
12264
|
+
|
|
12265
|
+
This means:
|
|
12266
|
+
- No shared mutable state between tests
|
|
12267
|
+
- No reliance on test execution order
|
|
12268
|
+
- No file system or environment pollution
|
|
12269
|
+
- Each test sets up its own dependencies
|
|
12270
|
+
|
|
12271
|
+
Test isolation enables parallel execution, makes failures easier to diagnose, and prevents cascading false failures when one test breaks.
|
|
11738
12272
|
|
|
11739
12273
|
## Parent Principle
|
|
11740
12274
|
|
|
11741
|
-
- [
|
|
12275
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
11742
12276
|
|
|
11743
12277
|
## Sub-Principles
|
|
12278
|
+
|
|
12279
|
+
- [Environment-Independent Tests](environment-independent-tests.md)
|
|
11744
12280
|
`
|
|
11745
12281
|
},
|
|
11746
12282
|
{
|
|
11747
|
-
slug: "
|
|
11748
|
-
content: `#
|
|
12283
|
+
slug: "boy-scout-rule",
|
|
12284
|
+
content: `# Boy Scout Rule
|
|
11749
12285
|
|
|
11750
|
-
|
|
12286
|
+
Always leave the code better than you found it.
|
|
11751
12287
|
|
|
11752
|
-
|
|
12288
|
+
When working in any area of the codebase, take the opportunity to make small improvements — clearer names, removed dead code, better structure — even if they're not directly related to the task at hand. These incremental improvements compound over time, preventing gradual decay and keeping the codebase healthy without requiring dedicated cleanup efforts.
|
|
11753
12289
|
|
|
11754
|
-
|
|
12290
|
+
The Boy Scout Rule is not a license for large-scale refactoring during unrelated work. Improvements should be small, obvious, and low-risk. If a cleanup is too large to include alongside the current task, capture it as a separate task instead.
|
|
11755
12291
|
|
|
11756
|
-
|
|
12292
|
+
## Parent Principle
|
|
11757
12293
|
|
|
11758
|
-
|
|
12294
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
11759
12295
|
|
|
11760
|
-
|
|
12296
|
+
## Sub-Principles
|
|
11761
12297
|
|
|
11762
|
-
|
|
12298
|
+
- (none)
|
|
12299
|
+
`
|
|
12300
|
+
},
|
|
12301
|
+
{
|
|
12302
|
+
slug: "atomic-commits",
|
|
12303
|
+
content: `# Atomic Commits
|
|
11763
12304
|
|
|
11764
|
-
|
|
11765
|
-
- Trade-offs between approaches are understood
|
|
11766
|
-
- The chosen direction has clear justification
|
|
11767
|
-
- Remaining uncertainty is about requirements, not design
|
|
12305
|
+
Each commit should tell a complete story, bundling implementation changes with their corresponding documentation updates.
|
|
11768
12306
|
|
|
11769
|
-
|
|
12307
|
+
When a task is completed, the commit deletes the task file, updates relevant facts to reflect the new reality, and removes any ideas that have been realized. This discipline ensures that any point in the commit history represents a coherent, self-documenting state of the project.
|
|
11770
12308
|
|
|
11771
|
-
|
|
12309
|
+
Clean commit history is essential because archaeology depends on it. Future humans and AI agents will traverse history to understand why decisions were made and how the system evolved.
|
|
11772
12310
|
|
|
11773
|
-
|
|
12311
|
+
## Parent Principle
|
|
12312
|
+
|
|
12313
|
+
- [Repository Hygiene](repository-hygiene.md)
|
|
12314
|
+
|
|
12315
|
+
## Sub-Principles
|
|
12316
|
+
|
|
12317
|
+
- [Traceable Decisions](traceable-decisions.md)
|
|
12318
|
+
`
|
|
12319
|
+
},
|
|
12320
|
+
{
|
|
12321
|
+
slug: "co-located-tests",
|
|
12322
|
+
content: `# Co-located Tests
|
|
12323
|
+
|
|
12324
|
+
Test files should live next to the code they test.
|
|
12325
|
+
|
|
12326
|
+
When tests are co-located with their source files, developers can immediately see what's tested and what isn't. Finding the test for a module becomes trivial—it's right there in the same directory. This proximity encourages writing tests as part of the development flow rather than as an afterthought, and makes it natural to update tests when modifying code.
|
|
11774
12327
|
|
|
11775
12328
|
## Parent Principle
|
|
11776
12329
|
|
|
11777
|
-
- [
|
|
12330
|
+
- [Intuitive Directory Structure](intuitive-directory-structure.md)
|
|
11778
12331
|
|
|
11779
12332
|
## Sub-Principles
|
|
11780
12333
|
|
|
@@ -11782,20 +12335,20 @@ Ideas should document the alternatives considered and why they were ruled out. T
|
|
|
11782
12335
|
`
|
|
11783
12336
|
},
|
|
11784
12337
|
{
|
|
11785
|
-
slug: "
|
|
11786
|
-
content: `#
|
|
12338
|
+
slug: "broken-windows",
|
|
12339
|
+
content: `# Broken Windows
|
|
11787
12340
|
|
|
11788
|
-
|
|
12341
|
+
Don't leave broken windows unrepaired.
|
|
11789
12342
|
|
|
11790
|
-
|
|
12343
|
+
A broken window — a bad name, a hack, a TODO that lingers, a test that's been skipped — signals that nobody cares. That signal invites more neglect. One shortcut becomes two, then ten, and the codebase quietly rots from the inside.
|
|
11791
12344
|
|
|
11792
|
-
|
|
12345
|
+
When you spot a broken window, fix it immediately if the fix is small. If it's too large, capture it as a task so it doesn't get forgotten. The key is to never normalise the damage. Even a comment acknowledging the problem ("this needs fixing because...") is better than silent acceptance.
|
|
11793
12346
|
|
|
11794
|
-
This
|
|
12347
|
+
This principle complements the [Boy Scout Rule](boy-scout-rule.md): the Boy Scout Rule encourages proactive improvement, while Broken Windows warns against tolerating known problems. Together they keep entropy at bay.
|
|
11795
12348
|
|
|
11796
12349
|
## Parent Principle
|
|
11797
12350
|
|
|
11798
|
-
- [
|
|
12351
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
11799
12352
|
|
|
11800
12353
|
## Sub-Principles
|
|
11801
12354
|
|
|
@@ -11803,97 +12356,184 @@ This is particularly important in agent-driven development. Agents cannot manual
|
|
|
11803
12356
|
`
|
|
11804
12357
|
},
|
|
11805
12358
|
{
|
|
11806
|
-
slug: "
|
|
11807
|
-
content: `#
|
|
12359
|
+
slug: "trunk-based-development",
|
|
12360
|
+
content: `# Trunk-Based Development
|
|
11808
12361
|
|
|
11809
|
-
|
|
12362
|
+
Dust is designed to support a non-branching workflow where developers commit directly to a single main branch.
|
|
11810
12363
|
|
|
11811
|
-
|
|
12364
|
+
In trunk-based development, teams collaborate on code in one primary branch rather than maintaining multiple long-lived feature branches. This eliminates merge conflicts, enables continuous integration, and keeps the codebase continuously releasable.
|
|
11812
12365
|
|
|
11813
|
-
|
|
12366
|
+
The \`dust loop claude\` command embodies this philosophy: agents pull from main, implement a task, and push directly back to main. There are no feature branches, no pull requests, no merge queues. Each commit is atomic and complete.
|
|
12367
|
+
|
|
12368
|
+
This approach scales through discipline rather than isolation. Feature flags and incremental changes replace long-running branches. The repository history becomes a linear sequence of working states.
|
|
12369
|
+
|
|
12370
|
+
See: https://trunkbaseddevelopment.com/
|
|
12371
|
+
|
|
12372
|
+
## Parent Principle
|
|
12373
|
+
|
|
12374
|
+
- [Repository Hygiene](repository-hygiene.md)
|
|
12375
|
+
|
|
12376
|
+
## Sub-Principles
|
|
12377
|
+
|
|
12378
|
+
(none)
|
|
12379
|
+
`
|
|
12380
|
+
},
|
|
12381
|
+
{
|
|
12382
|
+
slug: "environment-independent-tests",
|
|
12383
|
+
content: `# Environment-Independent Tests
|
|
12384
|
+
|
|
12385
|
+
Tests must produce the same result regardless of where they run. A test that passes locally but fails in CI (or vice versa) is a broken test.
|
|
12386
|
+
|
|
12387
|
+
Concretely, tests should never depend on:
|
|
12388
|
+
- Ambient environment variables (e.g. \`CLAUDECODE\`, \`CI\`, \`HOME\`)
|
|
12389
|
+
- The current working directory or filesystem layout of the host machine
|
|
12390
|
+
- Network availability or external services
|
|
12391
|
+
- The identity of the user or agent running the tests
|
|
12392
|
+
|
|
12393
|
+
When a function's behavior depends on environment variables, the test must explicitly control those variables (via \`stubEnv\`, dependency injection, or passing an \`env\` parameter) rather than relying on whatever happens to be set in the current shell.
|
|
12394
|
+
|
|
12395
|
+
## Parent Principle
|
|
12396
|
+
|
|
12397
|
+
- [Test Isolation](test-isolation.md)
|
|
12398
|
+
|
|
12399
|
+
## Sub-Principles
|
|
12400
|
+
|
|
12401
|
+
- (none)
|
|
12402
|
+
`
|
|
12403
|
+
},
|
|
12404
|
+
{
|
|
12405
|
+
slug: "comprehensive-assertions",
|
|
12406
|
+
content: `# Comprehensive Assertions
|
|
12407
|
+
|
|
12408
|
+
Assert the whole, not the parts.
|
|
12409
|
+
|
|
12410
|
+
When you break a complex object into many small assertions, a failure tells you *one thing that's wrong*. When you assert against the whole expected value, the diff tells you *what actually happened versus what you expected* — the full picture, in one glance.
|
|
12411
|
+
|
|
12412
|
+
Small assertions are like yes/no questions to a witness. A whole-object assertion is like asking "tell me what you saw."
|
|
11814
12413
|
|
|
11815
12414
|
## In practice
|
|
11816
12415
|
|
|
11817
|
-
|
|
12416
|
+
Collapse multiple partial assertions into one comprehensive assertion:
|
|
11818
12417
|
|
|
11819
12418
|
\`\`\`javascript
|
|
11820
|
-
//
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
})
|
|
12419
|
+
// Fragmented — each failure is a narrow keyhole
|
|
12420
|
+
expect(result.name).toBe("Alice");
|
|
12421
|
+
expect(result.age).toBe(30);
|
|
12422
|
+
expect(result.role).toBe("admin");
|
|
11825
12423
|
|
|
11826
|
-
//
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
},
|
|
11833
|
-
ideas: {}
|
|
11834
|
-
}
|
|
11835
|
-
}
|
|
11836
|
-
})
|
|
12424
|
+
// Whole — a failure diff tells the full story
|
|
12425
|
+
expect(result).toEqual({
|
|
12426
|
+
name: "Alice",
|
|
12427
|
+
age: 30,
|
|
12428
|
+
role: "admin",
|
|
12429
|
+
});
|
|
11837
12430
|
\`\`\`
|
|
11838
12431
|
|
|
11839
|
-
The
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
12432
|
+
If \`role\` is \`"user"\` and \`age\` is \`29\`, the fragmented version stops at the first failure. The whole-object assertion shows both discrepancies at once, in context.
|
|
12433
|
+
|
|
12434
|
+
The same applies to arrays:
|
|
12435
|
+
|
|
12436
|
+
\`\`\`javascript
|
|
12437
|
+
// Avoid: partial assertions that hide the actual state
|
|
12438
|
+
expect(array).toContain('apples')
|
|
12439
|
+
expect(array).toContain('oranges')
|
|
12440
|
+
|
|
12441
|
+
// Prefer: one assertion that reveals the full picture on failure
|
|
12442
|
+
expect(array).toEqual(['apples', 'oranges'])
|
|
12443
|
+
\`\`\`
|
|
11843
12444
|
|
|
11844
12445
|
## How to evaluate
|
|
11845
12446
|
|
|
11846
|
-
Work supports this principle when test
|
|
12447
|
+
Work supports this principle when test failures tell a rich story — showing the complete actual value alongside the complete expected value, so the reader can understand what happened without re-running anything.
|
|
12448
|
+
|
|
12449
|
+
## Parent Principle
|
|
12450
|
+
|
|
12451
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12452
|
+
|
|
12453
|
+
## Sub-Principles
|
|
12454
|
+
|
|
12455
|
+
- (none)
|
|
12456
|
+
`
|
|
12457
|
+
},
|
|
12458
|
+
{
|
|
12459
|
+
slug: "maintainable-codebase",
|
|
12460
|
+
content: `# Maintainable Codebase
|
|
12461
|
+
|
|
12462
|
+
The dust codebase should be easy to understand, modify, and extend.
|
|
12463
|
+
|
|
12464
|
+
This principle governs how we develop and maintain dust itself, separate from the principles that describe what dust offers its users. A well-maintained codebase enables rapid iteration, reduces bugs, and makes contributions easier.
|
|
11847
12465
|
|
|
11848
12466
|
## Parent Principle
|
|
11849
12467
|
|
|
12468
|
+
- [Agentic Flow State](agentic-flow-state.md)
|
|
12469
|
+
|
|
12470
|
+
## Sub-Principles
|
|
12471
|
+
|
|
11850
12472
|
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12473
|
+
- [Minimal Dependencies](minimal-dependencies.md)
|
|
12474
|
+
- [Intuitive Directory Structure](intuitive-directory-structure.md)
|
|
12475
|
+
- [Repository Hygiene](repository-hygiene.md)
|
|
12476
|
+
- [Naming Matters](naming-matters.md)
|
|
12477
|
+
- [Reasonably DRY](reasonably-dry.md)
|
|
12478
|
+
- [Make the Change Easy](make-the-change-easy.md)
|
|
12479
|
+
- [Boy Scout Rule](boy-scout-rule.md)
|
|
12480
|
+
- [Broken Windows](broken-windows.md)
|
|
12481
|
+
`
|
|
12482
|
+
},
|
|
12483
|
+
{
|
|
12484
|
+
slug: "context-window-efficiency",
|
|
12485
|
+
content: `# Context Window Efficiency
|
|
12486
|
+
|
|
12487
|
+
Dust should be designed with short attention spans in mind.
|
|
12488
|
+
|
|
12489
|
+
AI agents operate within limited context windows. Every token consumed by planning artifacts is a token unavailable for reasoning about code. Dust keeps artifacts concise and scannable so agents can quickly understand what needs to be done without wading through verbose documentation.
|
|
12490
|
+
|
|
12491
|
+
This means favoring brevity over completeness, using consistent structures that are fast to parse, and avoiding redundant information across files.
|
|
12492
|
+
|
|
12493
|
+
## Parent Principle
|
|
12494
|
+
|
|
12495
|
+
- [Agent Autonomy](agent-autonomy.md)
|
|
11851
12496
|
|
|
11852
12497
|
## Sub-Principles
|
|
11853
12498
|
|
|
11854
|
-
- (
|
|
12499
|
+
- [Progressive Disclosure](progressive-disclosure.md)
|
|
11855
12500
|
`
|
|
11856
12501
|
},
|
|
11857
12502
|
{
|
|
11858
|
-
slug: "
|
|
11859
|
-
content: `#
|
|
11860
|
-
|
|
11861
|
-
Dust should detect and enhance the experience for specific agents while remaining agnostic at its core.
|
|
11862
|
-
|
|
11863
|
-
While Dust has [Agent-Agnostic Design](agent-agnostic-design.md) and works with any capable agent, it can still optimize the "agent DX" (developer experience) when it detects a specific agent is being used. This means:
|
|
11864
|
-
|
|
11865
|
-
- **Detection** - Dust may detect which agent is running (e.g., Claude Code, Aider, Cursor) through environment variables, configuration, or other signals
|
|
11866
|
-
- **Enhancement** - Once detected, Dust can tailor its output format, prompts, or context to leverage that agent's specific strengths
|
|
11867
|
-
- **Graceful fallback** - When no specific agent is detected, Dust provides a generic experience that works with any agent
|
|
12503
|
+
slug: "human-ai-collaboration",
|
|
12504
|
+
content: `# Human-AI Collaboration
|
|
11868
12505
|
|
|
11869
|
-
|
|
12506
|
+
Dust exists to enable effective collaboration between humans and AI agents on complex projects.
|
|
11870
12507
|
|
|
11871
|
-
|
|
12508
|
+
The human is the CEO — they set direction, make strategic decisions, and check in when it matters. Dust is the PM — it manages the work, prepares context, and brings fully-researched questions to the human rather than expecting them to drive every detail. Agents are the developers — they read code, write changes, and iterate autonomously.
|
|
11872
12509
|
|
|
11873
|
-
|
|
12510
|
+
Today's AI coding tools keep humans in a tight loop with agents. Dust is designed to loosen that loop, so humans spend less time directing and more time deciding.
|
|
11874
12511
|
|
|
11875
12512
|
## Parent Principle
|
|
11876
12513
|
|
|
11877
|
-
- [
|
|
12514
|
+
- [Agentic Flow State](agentic-flow-state.md)
|
|
11878
12515
|
|
|
11879
12516
|
## Sub-Principles
|
|
11880
12517
|
|
|
11881
|
-
- (
|
|
12518
|
+
- [Agent Autonomy](agent-autonomy.md)
|
|
12519
|
+
- [Easy Adoption](easy-adoption.md)
|
|
12520
|
+
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
12521
|
+
- [Lightweight Planning](lightweight-planning.md)
|
|
11882
12522
|
`
|
|
11883
12523
|
},
|
|
11884
12524
|
{
|
|
11885
|
-
slug: "
|
|
11886
|
-
content: `#
|
|
12525
|
+
slug: "functional-core-imperative-shell",
|
|
12526
|
+
content: `# Functional Core, Imperative Shell
|
|
11887
12527
|
|
|
11888
|
-
|
|
12528
|
+
Separate code into a pure "functional core" and a thin "imperative shell." The core takes values in and returns values out, with no side effects. The shell handles I/O and wires things together.
|
|
11889
12529
|
|
|
11890
|
-
|
|
12530
|
+
Purely functional code makes some things easier to understand: because values don't change, you can call functions and know that only their return value matters—they don't change anything outside themselves.
|
|
11891
12531
|
|
|
11892
|
-
|
|
12532
|
+
The functional core contains business logic as pure functions that take values and return values. The imperative shell sits at the boundary, reading input, calling into the core, and performing side effects with the results. This keeps the majority of code easy to test (no mocks or stubs needed for pure functions) and makes the I/O surface area small and explicit.
|
|
11893
12533
|
|
|
11894
12534
|
## Parent Principle
|
|
11895
12535
|
|
|
11896
|
-
- [
|
|
12536
|
+
- [Decoupled Code](decoupled-code.md)
|
|
11897
12537
|
|
|
11898
12538
|
## Sub-Principles
|
|
11899
12539
|
|
|
@@ -11901,57 +12541,45 @@ Dust should help projects identify files that are too large, modules that are to
|
|
|
11901
12541
|
`
|
|
11902
12542
|
},
|
|
11903
12543
|
{
|
|
11904
|
-
slug: "
|
|
11905
|
-
content: `#
|
|
11906
|
-
|
|
11907
|
-
When a big test fails, it should be self-evident how to diagnose and fix the failure.
|
|
12544
|
+
slug: "keep-unit-tests-pure",
|
|
12545
|
+
content: `# Keep Unit Tests Pure
|
|
11908
12546
|
|
|
11909
|
-
|
|
12547
|
+
Unit tests (those run very frequently as part of a tight feedback loop) should be pure and side-effect free. A test is **not** a unit test if it:
|
|
11910
12548
|
|
|
11911
|
-
|
|
12549
|
+
- Accesses a database
|
|
12550
|
+
- Communicates over a network
|
|
12551
|
+
- Touches the file system
|
|
12552
|
+
- Cannot run concurrently with other tests
|
|
12553
|
+
- Requires special environment setup
|
|
11912
12554
|
|
|
11913
|
-
|
|
11914
|
-
\`\`\`javascript
|
|
11915
|
-
// Bad: "expected true, received false" — what events arrived?
|
|
11916
|
-
expect(events.some(e => e.type === 'check-passed')).toBe(true)
|
|
12555
|
+
"Unit tests" here means tests run frequently during development — not system tests, which intentionally exercise the full stack including I/O. Pure unit tests exercise only business logic, not infrastructure.
|
|
11917
12556
|
|
|
11918
|
-
|
|
11919
|
-
expect(events.map(e => e.type)).toContain('check-passed')
|
|
11920
|
-
\`\`\`
|
|
12557
|
+
The value of pure unit tests is that they are fast, deterministic, and isolate business logic from infrastructure concerns. When unit tests pass but integration or system tests fail, developers can immediately narrow the problem to the boundary layer — a diagnostic "binary chop" that accelerates debugging.
|
|
11921
12558
|
|
|
11922
|
-
|
|
11923
|
-
\`\`\`javascript
|
|
11924
|
-
// Bad: "expected 2, received 0" — what requests were captured?
|
|
11925
|
-
expect(requests.length).toBe(2)
|
|
12559
|
+
## Migration Guidance
|
|
11926
12560
|
|
|
11927
|
-
|
|
11928
|
-
expect(requests).toHaveLength(2) // vitest shows the array
|
|
11929
|
-
\`\`\`
|
|
12561
|
+
Where existing tests are impure (e.g. they spawn processes, write temporary files, or make network calls), prefer converting them to use in-memory alternatives — stubs, fakes, or dependency-injected doubles — rather than leaving them as-is. Opportunistic migration is fine; a big-bang rewrite is not required.
|
|
11930
12562
|
|
|
11931
|
-
|
|
11932
|
-
\`\`\`javascript
|
|
11933
|
-
// Bad: silently passes when settings is undefined
|
|
11934
|
-
if (settings) {
|
|
11935
|
-
expect(JSON.parse(settings).key).toBeDefined()
|
|
11936
|
-
}
|
|
12563
|
+
## Parent Principle
|
|
11937
12564
|
|
|
11938
|
-
|
|
11939
|
-
expect(settings).toBeDefined()
|
|
11940
|
-
const parsed = JSON.parse(settings!)
|
|
11941
|
-
expect(parsed.key).toBeDefined()
|
|
11942
|
-
\`\`\`
|
|
12565
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
11943
12566
|
|
|
11944
|
-
##
|
|
12567
|
+
## Sub-Principles
|
|
11945
12568
|
|
|
11946
|
-
|
|
12569
|
+
- (none)
|
|
12570
|
+
`
|
|
12571
|
+
},
|
|
12572
|
+
{
|
|
12573
|
+
slug: "runtime-agnostic-tests",
|
|
12574
|
+
content: `# Runtime Agnostic Tests
|
|
11947
12575
|
|
|
11948
|
-
|
|
12576
|
+
Dust's test suite should work across JavaScript runtimes.
|
|
11949
12577
|
|
|
11950
|
-
|
|
12578
|
+
Tests should use standard JavaScript testing patterns that work across Node.js, Bun, and other runtimes. Avoiding runtime-specific test APIs ensures the project can leverage different runtimes' advantages while maintaining broad compatibility.
|
|
11951
12579
|
|
|
11952
12580
|
## Parent Principle
|
|
11953
12581
|
|
|
11954
|
-
- [
|
|
12582
|
+
- [Minimal Dependencies](minimal-dependencies.md)
|
|
11955
12583
|
|
|
11956
12584
|
## Sub-Principles
|
|
11957
12585
|
|
|
@@ -11959,48 +12587,35 @@ Work supports this principle when every assertion in a system or integration tes
|
|
|
11959
12587
|
`
|
|
11960
12588
|
},
|
|
11961
12589
|
{
|
|
11962
|
-
slug: "
|
|
11963
|
-
content: `#
|
|
11964
|
-
|
|
11965
|
-
The agent is the developer. The human is the CEO. Dust is the PM.
|
|
12590
|
+
slug: "unsurprising-ux",
|
|
12591
|
+
content: `# Unsurprising UX
|
|
11966
12592
|
|
|
11967
|
-
|
|
12593
|
+
The user interface should be as "guessable" as possible.
|
|
11968
12594
|
|
|
11969
|
-
|
|
12595
|
+
Following the [Principle of Least Astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment), users form expectations about how a tool will behave based on conventions, prior experience, and intuition. Dust's interface (including the CLI) should match those expectations wherever possible. If users are observed trying to use the interface in ways we didn't anticipate, the interface should be adjusted to meet their expectations — even if that means supporting many ways of achieving the same result.
|
|
11970
12596
|
|
|
11971
|
-
|
|
12597
|
+
Surprising behavior erodes trust and slows people down. Unsurprising behavior lets users stay in flow.
|
|
11972
12598
|
|
|
11973
12599
|
## Parent Principle
|
|
11974
12600
|
|
|
11975
|
-
- [
|
|
12601
|
+
- [Easy Adoption](easy-adoption.md)
|
|
11976
12602
|
|
|
11977
12603
|
## Sub-Principles
|
|
11978
12604
|
|
|
11979
|
-
-
|
|
11980
|
-
- [Fast Feedback Loops](fast-feedback-loops.md)
|
|
11981
|
-
- [Slow Feedback Coping](slow-feedback-coping.md)
|
|
11982
|
-
- [Development Traceability](development-traceability.md)
|
|
11983
|
-
- [Context-Optimised Code](context-optimised-code.md)
|
|
11984
|
-
- [Exploratory Tooling](exploratory-tooling.md)
|
|
11985
|
-
- [Debugging Tooling](debugging-tooling.md)
|
|
11986
|
-
- [Self-Contained Repository](self-contained-repository.md)
|
|
12605
|
+
- (none)
|
|
11987
12606
|
`
|
|
11988
12607
|
},
|
|
11989
12608
|
{
|
|
11990
|
-
slug: "
|
|
11991
|
-
content: `#
|
|
11992
|
-
|
|
11993
|
-
Don't leave broken windows unrepaired.
|
|
11994
|
-
|
|
11995
|
-
A broken window — a bad name, a hack, a TODO that lingers, a test that's been skipped — signals that nobody cares. That signal invites more neglect. One shortcut becomes two, then ten, and the codebase quietly rots from the inside.
|
|
12609
|
+
slug: "unit-test-coverage",
|
|
12610
|
+
content: `# Unit Test Coverage
|
|
11996
12611
|
|
|
11997
|
-
|
|
12612
|
+
Complete unit test coverage ensures low-level tests give users direct feedback as they change the code.
|
|
11998
12613
|
|
|
11999
|
-
|
|
12614
|
+
Excluding system tests from coverage reporting focuses attention on unit tests - the tests that provide the fastest, most specific feedback. When coverage tools only measure unit tests, developers can quickly identify which parts of the codebase lack fine-grained test protection.
|
|
12000
12615
|
|
|
12001
12616
|
## Parent Principle
|
|
12002
12617
|
|
|
12003
|
-
- [
|
|
12618
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12004
12619
|
|
|
12005
12620
|
## Sub-Principles
|
|
12006
12621
|
|
|
@@ -12008,18 +12623,22 @@ This principle complements the [Boy Scout Rule](boy-scout-rule.md): the Boy Scou
|
|
|
12008
12623
|
`
|
|
12009
12624
|
},
|
|
12010
12625
|
{
|
|
12011
|
-
slug: "
|
|
12012
|
-
content: `#
|
|
12626
|
+
slug: "cross-platform-compatibility",
|
|
12627
|
+
content: `# Cross-Platform Compatibility
|
|
12013
12628
|
|
|
12014
|
-
Dust should
|
|
12629
|
+
Dust should work consistently across operating systems: Linux, macOS, and Windows.
|
|
12015
12630
|
|
|
12016
|
-
|
|
12631
|
+
This means:
|
|
12632
|
+
- Avoiding platform-specific shell commands or syntax
|
|
12633
|
+
- Using cross-platform path handling
|
|
12634
|
+
- Testing on multiple platforms when possible
|
|
12635
|
+
- Documenting any platform-specific limitations
|
|
12017
12636
|
|
|
12018
|
-
|
|
12637
|
+
Cross-platform support broadens adoption and ensures teams with mixed environments can collaborate effectively.
|
|
12019
12638
|
|
|
12020
12639
|
## Parent Principle
|
|
12021
12640
|
|
|
12022
|
-
- [
|
|
12641
|
+
- [Easy Adoption](easy-adoption.md)
|
|
12023
12642
|
|
|
12024
12643
|
## Sub-Principles
|
|
12025
12644
|
|
|
@@ -12027,34 +12646,33 @@ This layered approach keeps initial reads lightweight while preserving access to
|
|
|
12027
12646
|
`
|
|
12028
12647
|
},
|
|
12029
12648
|
{
|
|
12030
|
-
slug: "
|
|
12031
|
-
content: `#
|
|
12032
|
-
|
|
12033
|
-
Dust aims to be a minimal, low-overhead planning system that stays relevant over time.
|
|
12649
|
+
slug: "vcs-independence",
|
|
12650
|
+
content: `# VCS Independence
|
|
12034
12651
|
|
|
12035
|
-
|
|
12652
|
+
Dust should work independently of any specific version control system.
|
|
12036
12653
|
|
|
12037
|
-
|
|
12654
|
+
While git is common, dust's core functionality should not require git. This enables use in repositories using other VCS (Mercurial, SVN, Perforce) or in non-VCS workflows.
|
|
12038
12655
|
|
|
12039
12656
|
## Parent Principle
|
|
12040
12657
|
|
|
12041
|
-
- [
|
|
12658
|
+
- [Easy Adoption](easy-adoption.md)
|
|
12042
12659
|
|
|
12043
12660
|
## Sub-Principles
|
|
12044
12661
|
|
|
12045
|
-
-
|
|
12046
|
-
- [Some Big Design Up Front](some-big-design-up-front.md)
|
|
12662
|
+
- (none)
|
|
12047
12663
|
`
|
|
12048
12664
|
},
|
|
12049
12665
|
{
|
|
12050
|
-
slug: "
|
|
12051
|
-
content: `#
|
|
12666
|
+
slug: "self-contained-repository",
|
|
12667
|
+
content: `# Self-Contained Repository
|
|
12052
12668
|
|
|
12053
|
-
|
|
12669
|
+
Where possible, developers and agents should have everything they need to be productive, within the repository.
|
|
12054
12670
|
|
|
12055
|
-
|
|
12671
|
+
No third-party tools should be required beyond those that can be installed with a single command defined in the repository. Setup instructions, scripts, configuration, and dependencies should all live in version control so that cloning the repo and running a single install command is sufficient to start working. This eliminates onboarding friction, reduces "works on my machine" issues, and is especially important for agents — who cannot browse the web to find missing tools or ask colleagues how to set things up.
|
|
12056
12672
|
|
|
12057
|
-
|
|
12673
|
+
## Applicability
|
|
12674
|
+
|
|
12675
|
+
Internal
|
|
12058
12676
|
|
|
12059
12677
|
## Parent Principle
|
|
12060
12678
|
|
|
@@ -12066,12 +12684,12 @@ Dust should help projects measure and improve their test coverage, flag untested
|
|
|
12066
12684
|
`
|
|
12067
12685
|
},
|
|
12068
12686
|
{
|
|
12069
|
-
slug: "
|
|
12070
|
-
content: `#
|
|
12687
|
+
slug: "minimal-dependencies",
|
|
12688
|
+
content: `# Minimal Dependencies
|
|
12071
12689
|
|
|
12072
|
-
|
|
12690
|
+
Dust should avoid coupling to specific tools so we can switch to better alternatives as they emerge.
|
|
12073
12691
|
|
|
12074
|
-
|
|
12692
|
+
By keeping dependencies minimal and using standard APIs where possible, we maintain the freedom to adopt new tools without major rewrites. This applies to runtimes, test frameworks, build tools, and other infrastructure choices.
|
|
12075
12693
|
|
|
12076
12694
|
## Parent Principle
|
|
12077
12695
|
|
|
@@ -12079,20 +12697,26 @@ When files that serve similar purposes are grouped together, the codebase become
|
|
|
12079
12697
|
|
|
12080
12698
|
## Sub-Principles
|
|
12081
12699
|
|
|
12082
|
-
- [
|
|
12700
|
+
- [Runtime Agnostic Tests](runtime-agnostic-tests.md)
|
|
12083
12701
|
`
|
|
12084
12702
|
},
|
|
12085
12703
|
{
|
|
12086
|
-
slug: "
|
|
12087
|
-
content: `#
|
|
12704
|
+
slug: "agent-specific-enhancement",
|
|
12705
|
+
content: `# Agent-Specific Enhancement
|
|
12088
12706
|
|
|
12089
|
-
|
|
12707
|
+
Dust should detect and enhance the experience for specific agents while remaining agnostic at its core.
|
|
12090
12708
|
|
|
12091
|
-
|
|
12709
|
+
While Dust has [Agent-Agnostic Design](agent-agnostic-design.md) and works with any capable agent, it can still optimize the "agent DX" (developer experience) when it detects a specific agent is being used. This means:
|
|
12092
12710
|
|
|
12093
|
-
|
|
12711
|
+
- **Detection** - Dust may detect which agent is running (e.g., Claude Code, Aider, Cursor) through environment variables, configuration, or other signals
|
|
12712
|
+
- **Enhancement** - Once detected, Dust can tailor its output format, prompts, or context to leverage that agent's specific strengths
|
|
12713
|
+
- **Graceful fallback** - When no specific agent is detected, Dust provides a generic experience that works with any agent
|
|
12094
12714
|
|
|
12095
|
-
|
|
12715
|
+
This principle complements Agent-Agnostic Design: the core functionality never requires a specific agent, but the experience improves when one is recognized.
|
|
12716
|
+
|
|
12717
|
+
## Applicability
|
|
12718
|
+
|
|
12719
|
+
Internal
|
|
12096
12720
|
|
|
12097
12721
|
## Parent Principle
|
|
12098
12722
|
|
|
@@ -12104,48 +12728,53 @@ Note: This principle directly supports [Lightweight Planning](lightweight-planni
|
|
|
12104
12728
|
`
|
|
12105
12729
|
},
|
|
12106
12730
|
{
|
|
12107
|
-
slug: "
|
|
12108
|
-
content: `#
|
|
12109
|
-
|
|
12110
|
-
Dust should provide fast feedback loops for developers.
|
|
12111
|
-
|
|
12112
|
-
Scripts and tooling should execute quickly so developers can iterate rapidly. Slow feedback discourages frequent validation and leads to larger, riskier changes. Fast feedback enables small, confident steps.
|
|
12731
|
+
slug: "self-diagnosing-tests",
|
|
12732
|
+
content: `# Self-Diagnosing Tests
|
|
12113
12733
|
|
|
12114
|
-
|
|
12734
|
+
When a big test fails, it should be self-evident how to diagnose and fix the failure.
|
|
12115
12735
|
|
|
12116
|
-
-
|
|
12736
|
+
The more moving parts a test has — end-to-end, system, integration — the more critical this becomes. A test that fails with \`expected true, received false\` forces the developer (or agent) to re-run, add logging, and guess. A test that fails with a rich diff showing the actual state versus the expected state turns diagnosis into reading.
|
|
12117
12737
|
|
|
12118
|
-
##
|
|
12738
|
+
## Anti-patterns
|
|
12119
12739
|
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
slug: "dependency-injection",
|
|
12125
|
-
content: `# Dependency Injection
|
|
12740
|
+
**Boolean flattening** — collapsing a rich value into true/false before asserting:
|
|
12741
|
+
\`\`\`javascript
|
|
12742
|
+
// Bad: "expected true, received false" — what events arrived?
|
|
12743
|
+
expect(events.some(e => e.type === 'check-passed')).toBe(true)
|
|
12126
12744
|
|
|
12127
|
-
|
|
12745
|
+
// Good: shows the actual event types on failure
|
|
12746
|
+
expect(events.map(e => e.type)).toContain('check-passed')
|
|
12747
|
+
\`\`\`
|
|
12128
12748
|
|
|
12129
|
-
|
|
12749
|
+
**Length-only assertions** — checking count without showing contents:
|
|
12750
|
+
\`\`\`javascript
|
|
12751
|
+
// Bad: "expected 2, received 0" — what requests were captured?
|
|
12752
|
+
expect(requests.length).toBe(2)
|
|
12130
12753
|
|
|
12131
|
-
|
|
12754
|
+
// Good: shows the actual requests on failure
|
|
12755
|
+
expect(requests).toHaveLength(2) // vitest shows the array
|
|
12756
|
+
\`\`\`
|
|
12132
12757
|
|
|
12133
|
-
|
|
12758
|
+
**Silent guards** — using \`if\` where an assertion belongs:
|
|
12759
|
+
\`\`\`javascript
|
|
12760
|
+
// Bad: silently passes when settings is undefined
|
|
12761
|
+
if (settings) {
|
|
12762
|
+
expect(JSON.parse(settings).key).toBeDefined()
|
|
12763
|
+
}
|
|
12134
12764
|
|
|
12135
|
-
|
|
12765
|
+
// Good: fails explicitly if settings is missing
|
|
12766
|
+
expect(settings).toBeDefined()
|
|
12767
|
+
const parsed = JSON.parse(settings!)
|
|
12768
|
+
expect(parsed.key).toBeDefined()
|
|
12769
|
+
\`\`\`
|
|
12136
12770
|
|
|
12137
|
-
##
|
|
12771
|
+
## The test
|
|
12138
12772
|
|
|
12139
|
-
-
|
|
12140
|
-
`
|
|
12141
|
-
},
|
|
12142
|
-
{
|
|
12143
|
-
slug: "reproducible-checks",
|
|
12144
|
-
content: `# Reproducible Checks
|
|
12773
|
+
If a test fails, can a developer who has never seen the code identify the problem from the failure output alone — without re-running, adding console.logs, or reading the test source? The closer to "yes", the better.
|
|
12145
12774
|
|
|
12146
|
-
|
|
12775
|
+
## How to evaluate
|
|
12147
12776
|
|
|
12148
|
-
|
|
12777
|
+
Work supports this principle when every assertion in a system or integration test would, on failure, reveal the actual state richly enough to guide a fix. Bare boolean checks, length-only assertions, and silent conditional guards are violations.
|
|
12149
12778
|
|
|
12150
12779
|
## Parent Principle
|
|
12151
12780
|
|
|
@@ -12176,45 +12805,40 @@ Strategies include separating fast and slow test suites, running slow checks asy
|
|
|
12176
12805
|
`
|
|
12177
12806
|
},
|
|
12178
12807
|
{
|
|
12179
|
-
slug: "
|
|
12180
|
-
content: `#
|
|
12808
|
+
slug: "agentic-flow-state",
|
|
12809
|
+
content: `# Agentic Flow State
|
|
12181
12810
|
|
|
12182
|
-
|
|
12811
|
+
Flow is the mental state where work becomes effortless - where you're fully immersed, losing track of time, operating at peak performance. Psychologist Mihaly Csikszentmihalyi identified three conditions that create flow: clear goals, immediate feedback, and challenge-skill balance.
|
|
12183
12812
|
|
|
12184
|
-
|
|
12813
|
+
For AI agents, achieving flow state means staying engaged and productive without interruption. Agents enter flow when they have optimal context, comprehensive guard rails, and minimal friction. Context window optimization ensures agents have exactly what they need without cognitive overload. In-session guard rails prevent agents from straying off course or making mistakes that break their momentum.
|
|
12814
|
+
|
|
12815
|
+
Dust's design targets these conditions directly:
|
|
12816
|
+
|
|
12817
|
+
- **Clear goals**: Task files and lightweight planning give you a concrete target. You know exactly what you're building next.
|
|
12818
|
+
- **Immediate feedback**: Fast feedback loops let you see results quickly. Each change confirms you're on track or shows you what to adjust.
|
|
12819
|
+
- **Challenge-skill balance**: Small units of work and agent autonomy keep you in the zone - challenged enough to stay engaged, supported enough to succeed.
|
|
12820
|
+
- **Context window efficiency**: Progressive disclosure and artifact summarization ensure agents have the right context without overflow.
|
|
12821
|
+
- **Comprehensive guard rails**: Lint rules, type checks, and automated validation catch mistakes before they compound.
|
|
12822
|
+
|
|
12823
|
+
Everything dust does serves flow. When agents stay in flow, they produce better work, sustain their momentum, and complete tasks autonomously.
|
|
12185
12824
|
|
|
12186
12825
|
## Parent Principle
|
|
12187
12826
|
|
|
12188
|
-
-
|
|
12827
|
+
- (none)
|
|
12189
12828
|
|
|
12190
12829
|
## Sub-Principles
|
|
12191
12830
|
|
|
12192
|
-
- [
|
|
12193
|
-
- [
|
|
12194
|
-
- [Fast Feedback](fast-feedback.md)
|
|
12195
|
-
- [Lint Everything](lint-everything.md)
|
|
12196
|
-
- [Readable Test Data](readable-test-data.md)
|
|
12197
|
-
- [Reproducible Checks](reproducible-checks.md)
|
|
12198
|
-
- [Stop the Line](stop-the-line.md)
|
|
12199
|
-
- [Keep Unit Tests Pure](keep-unit-tests-pure.md)
|
|
12200
|
-
- [Test Isolation](test-isolation.md)
|
|
12201
|
-
- [Self-Diagnosing Tests](self-diagnosing-tests.md)
|
|
12202
|
-
- [Unit Test Coverage](unit-test-coverage.md)
|
|
12831
|
+
- [Human-AI Collaboration](human-ai-collaboration.md)
|
|
12832
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
12203
12833
|
`
|
|
12204
12834
|
},
|
|
12205
12835
|
{
|
|
12206
|
-
slug: "
|
|
12207
|
-
content: `#
|
|
12208
|
-
|
|
12209
|
-
Tests should not interfere with one another. Each test must be independently runnable and produce the same result regardless of execution order or which other tests run alongside it.
|
|
12836
|
+
slug: "reproducible-checks",
|
|
12837
|
+
content: `# Reproducible Checks
|
|
12210
12838
|
|
|
12211
|
-
|
|
12212
|
-
- No shared mutable state between tests
|
|
12213
|
-
- No reliance on test execution order
|
|
12214
|
-
- No file system or environment pollution
|
|
12215
|
-
- Each test sets up its own dependencies
|
|
12839
|
+
Every check must produce the same result regardless of who runs it, when, or on what machine. If a check passes for one developer but fails for another, the check is broken.
|
|
12216
12840
|
|
|
12217
|
-
|
|
12841
|
+
Concretely, checks should pin their tool versions via the project's dependency manager (e.g. \`devDependencies\`) rather than relying on \`npx\`/\`bunx\` to fetch the latest version at runtime. Unpinned versions introduce non-determinism — a check that passed yesterday may fail today due to a tool upgrade that nobody chose to adopt.
|
|
12218
12842
|
|
|
12219
12843
|
## Parent Principle
|
|
12220
12844
|
|
|
@@ -12222,70 +12846,52 @@ Test isolation enables parallel execution, makes failures easier to diagnose, an
|
|
|
12222
12846
|
|
|
12223
12847
|
## Sub-Principles
|
|
12224
12848
|
|
|
12225
|
-
-
|
|
12849
|
+
- (none)
|
|
12226
12850
|
`
|
|
12227
12851
|
},
|
|
12228
12852
|
{
|
|
12229
|
-
slug: "
|
|
12230
|
-
content: `#
|
|
12853
|
+
slug: "task-first-workflow",
|
|
12854
|
+
content: `# Task-First Workflow
|
|
12231
12855
|
|
|
12232
|
-
|
|
12856
|
+
Work should be captured as a task before implementation begins, creating traceability between intent and outcome.
|
|
12233
12857
|
|
|
12234
|
-
This
|
|
12858
|
+
This discipline ensures that every change has a documented purpose. The commit history shows pairs of "Add task" followed by implementation, making it easy to understand why each change was made. It also prevents scope creep by defining boundaries before work starts.
|
|
12235
12859
|
|
|
12236
12860
|
## Parent Principle
|
|
12237
12861
|
|
|
12238
|
-
- [
|
|
12862
|
+
- [Lightweight Planning](lightweight-planning.md)
|
|
12239
12863
|
|
|
12240
12864
|
## Sub-Principles
|
|
12241
12865
|
|
|
12242
|
-
-
|
|
12243
|
-
- [Trunk-Based Development](trunk-based-development.md)
|
|
12866
|
+
- (none)
|
|
12244
12867
|
`
|
|
12245
12868
|
},
|
|
12246
12869
|
{
|
|
12247
|
-
slug: "
|
|
12248
|
-
content: `#
|
|
12249
|
-
|
|
12250
|
-
Flow is the mental state where work becomes effortless - where you're fully immersed, losing track of time, operating at peak performance. Psychologist Mihaly Csikszentmihalyi identified three conditions that create flow: clear goals, immediate feedback, and challenge-skill balance.
|
|
12870
|
+
slug: "ideal-agent-developer-experience",
|
|
12871
|
+
content: `# Ideal Agent Developer Experience
|
|
12251
12872
|
|
|
12252
|
-
|
|
12873
|
+
The agent is the developer. The human is the CEO. Dust is the PM.
|
|
12253
12874
|
|
|
12254
|
-
|
|
12875
|
+
With today's AI coding assistants, the human is stuck in a tight loop with agents — constantly directing, reviewing, and course-correcting. Dust is designed to relieve humans from this tight loop. Like an assistant to a CEO, dust predominantly brings fully-researched questions and well-prepared work to the human, rather than expecting the human to drive every decision. The human checks in less frequently, and when they do, they make high-leverage strategic calls rather than micromanaging implementation.
|
|
12255
12876
|
|
|
12256
|
-
|
|
12257
|
-
- **Immediate feedback**: Fast feedback loops let you see results quickly. Each change confirms you're on track or shows you what to adjust.
|
|
12258
|
-
- **Challenge-skill balance**: Small units of work and agent autonomy keep you in the zone - challenged enough to stay engaged, supported enough to succeed.
|
|
12259
|
-
- **Context window efficiency**: Progressive disclosure and artifact summarization ensure agents have the right context without overflow.
|
|
12260
|
-
- **Comprehensive guard rails**: Lint rules, type checks, and automated validation catch mistakes before they compound.
|
|
12877
|
+
For this to work, the agent's development environment must be excellent. The agent reads the code, writes changes, runs the checks, and iterates until the task is done. Everything about the codebase and its tooling either helps or hinders that process. Comprehensive tests are the agent's only way to verify correctness. Fast feedback loops are the agent's iteration speed. Structured logs are the agent's eyes into runtime behaviour. Small, well-organised files are what fit in the agent's context window. Exploratory and debugging tools are how the agent navigates and diagnoses without trial and error.
|
|
12261
12878
|
|
|
12262
|
-
|
|
12879
|
+
Each sub-principle represents a different aspect of the ideal agent developer setup. The better these are, the less the human needs to be in the loop.
|
|
12263
12880
|
|
|
12264
12881
|
## Parent Principle
|
|
12265
12882
|
|
|
12266
|
-
- (none)
|
|
12267
|
-
|
|
12268
|
-
## Sub-Principles
|
|
12269
|
-
|
|
12270
12883
|
- [Human-AI Collaboration](human-ai-collaboration.md)
|
|
12271
|
-
- [Maintainable Codebase](maintainable-codebase.md)
|
|
12272
|
-
`
|
|
12273
|
-
},
|
|
12274
|
-
{
|
|
12275
|
-
slug: "stop-the-line",
|
|
12276
|
-
content: `# Stop the Line
|
|
12277
|
-
|
|
12278
|
-
Any worker — human or agent — should halt and fix a problem the moment they detect it, rather than letting defects propagate downstream.
|
|
12279
|
-
|
|
12280
|
-
Originating from the Toyota production system, "Stop the Line" empowers every participant to pause work immediately upon identifying a defect, failing check, or safety hazard. Problems are cheaper to fix at their source than after they've compounded through later stages. In the context of dust, this means agents and humans alike should treat broken checks, test failures, and lint errors as blockers that demand immediate attention — not warnings to be deferred.
|
|
12281
|
-
|
|
12282
|
-
## Parent Principle
|
|
12283
|
-
|
|
12284
|
-
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12285
12884
|
|
|
12286
12885
|
## Sub-Principles
|
|
12287
12886
|
|
|
12288
|
-
- (
|
|
12887
|
+
- [Comprehensive Test Coverage](comprehensive-test-coverage.md)
|
|
12888
|
+
- [Fast Feedback Loops](fast-feedback-loops.md)
|
|
12889
|
+
- [Slow Feedback Coping](slow-feedback-coping.md)
|
|
12890
|
+
- [Development Traceability](development-traceability.md)
|
|
12891
|
+
- [Context-Optimised Code](context-optimised-code.md)
|
|
12892
|
+
- [Exploratory Tooling](exploratory-tooling.md)
|
|
12893
|
+
- [Debugging Tooling](debugging-tooling.md)
|
|
12894
|
+
- [Self-Contained Repository](self-contained-repository.md)
|
|
12289
12895
|
`
|
|
12290
12896
|
},
|
|
12291
12897
|
{
|
|
@@ -12312,21 +12918,26 @@ Internal
|
|
|
12312
12918
|
`
|
|
12313
12919
|
},
|
|
12314
12920
|
{
|
|
12315
|
-
slug: "
|
|
12316
|
-
content: `#
|
|
12921
|
+
slug: "agent-autonomy",
|
|
12922
|
+
content: `# Agent Autonomy
|
|
12317
12923
|
|
|
12318
|
-
|
|
12924
|
+
Dust exists to enable AI agents to produce work autonomously.
|
|
12319
12925
|
|
|
12320
|
-
|
|
12926
|
+
With sufficient planning and small enough units, this works much better in practice.
|
|
12321
12927
|
|
|
12322
12928
|
## Parent Principle
|
|
12323
12929
|
|
|
12324
|
-
- [
|
|
12930
|
+
- [Human-AI Collaboration](human-ai-collaboration.md)
|
|
12325
12931
|
|
|
12326
12932
|
## Sub-Principles
|
|
12327
12933
|
|
|
12328
|
-
- [
|
|
12329
|
-
- [
|
|
12934
|
+
- [Actionable Errors](actionable-errors.md)
|
|
12935
|
+
- [Batteries Included](batteries-included.md)
|
|
12936
|
+
- [Agent-Agnostic Design](agent-agnostic-design.md)
|
|
12937
|
+
- [Agent Context Inference](agent-context-inference.md)
|
|
12938
|
+
- [Agent-Specific Enhancement](agent-specific-enhancement.md)
|
|
12939
|
+
- [Context Window Efficiency](context-window-efficiency.md)
|
|
12940
|
+
- [Small Units](small-units.md)
|
|
12330
12941
|
`
|
|
12331
12942
|
},
|
|
12332
12943
|
{
|
|
@@ -12353,33 +12964,14 @@ Still use mocks selectively—mainly to assert something is called (e.g., teleme
|
|
|
12353
12964
|
`
|
|
12354
12965
|
},
|
|
12355
12966
|
{
|
|
12356
|
-
slug: "
|
|
12357
|
-
content: `#
|
|
12358
|
-
|
|
12359
|
-
Separate code into a pure "functional core" and a thin "imperative shell." The core takes values in and returns values out, with no side effects. The shell handles I/O and wires things together.
|
|
12360
|
-
|
|
12361
|
-
Purely functional code makes some things easier to understand: because values don't change, you can call functions and know that only their return value matters—they don't change anything outside themselves.
|
|
12362
|
-
|
|
12363
|
-
The functional core contains business logic as pure functions that take values and return values. The imperative shell sits at the boundary, reading input, calling into the core, and performing side effects with the results. This keeps the majority of code easy to test (no mocks or stubs needed for pure functions) and makes the I/O surface area small and explicit.
|
|
12364
|
-
|
|
12365
|
-
## Parent Principle
|
|
12366
|
-
|
|
12367
|
-
- [Decoupled Code](decoupled-code.md)
|
|
12368
|
-
|
|
12369
|
-
## Sub-Principles
|
|
12370
|
-
|
|
12371
|
-
- (none)
|
|
12372
|
-
`
|
|
12373
|
-
},
|
|
12374
|
-
{
|
|
12375
|
-
slug: "development-traceability",
|
|
12376
|
-
content: `# Development Traceability
|
|
12967
|
+
slug: "debugging-tooling",
|
|
12968
|
+
content: `# Debugging Tooling
|
|
12377
12969
|
|
|
12378
|
-
|
|
12970
|
+
Agents need effective tools for diagnosing and fixing issues without manual intervention.
|
|
12379
12971
|
|
|
12380
|
-
|
|
12972
|
+
Traditional debugging relies on breakpoints, stepping through code, and interactive inspection — techniques that work well for humans but poorly for agents. Agents need debugging tools that produce readable, structured output: stack traces with context, diff-friendly error messages, reproducible test cases, and diagnostic commands that can be run non-interactively.
|
|
12381
12973
|
|
|
12382
|
-
Dust should
|
|
12974
|
+
Dust should help projects adopt agent-friendly debugging practices: structured error output, diagnostic scripts, snapshot testing for complex state, and tools that turn vague symptoms into specific, actionable information.
|
|
12383
12975
|
|
|
12384
12976
|
## Applicability
|
|
12385
12977
|
|
|
@@ -12395,45 +12987,16 @@ Internal
|
|
|
12395
12987
|
`
|
|
12396
12988
|
},
|
|
12397
12989
|
{
|
|
12398
|
-
slug: "
|
|
12399
|
-
content: `#
|
|
12400
|
-
|
|
12401
|
-
Unit tests (those run very frequently as part of a tight feedback loop) should be pure and side-effect free. A test is **not** a unit test if it:
|
|
12402
|
-
|
|
12403
|
-
- Accesses a database
|
|
12404
|
-
- Communicates over a network
|
|
12405
|
-
- Touches the file system
|
|
12406
|
-
- Cannot run concurrently with other tests
|
|
12407
|
-
- Requires special environment setup
|
|
12408
|
-
|
|
12409
|
-
"Unit tests" here means tests run frequently during development — not system tests, which intentionally exercise the full stack including I/O. Pure unit tests exercise only business logic, not infrastructure.
|
|
12410
|
-
|
|
12411
|
-
The value of pure unit tests is that they are fast, deterministic, and isolate business logic from infrastructure concerns. When unit tests pass but integration or system tests fail, developers can immediately narrow the problem to the boundary layer — a diagnostic "binary chop" that accelerates debugging.
|
|
12412
|
-
|
|
12413
|
-
## Migration Guidance
|
|
12414
|
-
|
|
12415
|
-
Where existing tests are impure (e.g. they spawn processes, write temporary files, or make network calls), prefer converting them to use in-memory alternatives — stubs, fakes, or dependency-injected doubles — rather than leaving them as-is. Opportunistic migration is fine; a big-bang rewrite is not required.
|
|
12416
|
-
|
|
12417
|
-
## Parent Principle
|
|
12418
|
-
|
|
12419
|
-
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12420
|
-
|
|
12421
|
-
## Sub-Principles
|
|
12422
|
-
|
|
12423
|
-
- (none)
|
|
12424
|
-
`
|
|
12425
|
-
},
|
|
12426
|
-
{
|
|
12427
|
-
slug: "co-located-tests",
|
|
12428
|
-
content: `# Co-located Tests
|
|
12990
|
+
slug: "consistent-naming",
|
|
12991
|
+
content: `# Consistent Naming
|
|
12429
12992
|
|
|
12430
|
-
|
|
12993
|
+
Names should follow established conventions within each category to reduce cognitive load.
|
|
12431
12994
|
|
|
12432
|
-
|
|
12995
|
+
Principles use Title Case. File names use kebab-case. Commands use lowercase with hyphens. When naming conventions exist, follow them. When they don't, establish one and apply it consistently. Inconsistent naming creates friction for both humans and AI agents trying to predict or recall identifiers.
|
|
12433
12996
|
|
|
12434
12997
|
## Parent Principle
|
|
12435
12998
|
|
|
12436
|
-
- [
|
|
12999
|
+
- [Naming Matters](naming-matters.md)
|
|
12437
13000
|
|
|
12438
13001
|
## Sub-Principles
|
|
12439
13002
|
|
|
@@ -12441,180 +13004,156 @@ When tests are co-located with their source files, developers can immediately se
|
|
|
12441
13004
|
`
|
|
12442
13005
|
},
|
|
12443
13006
|
{
|
|
12444
|
-
slug: "
|
|
12445
|
-
content: `#
|
|
13007
|
+
slug: "lightweight-planning",
|
|
13008
|
+
content: `# Lightweight Planning
|
|
12446
13009
|
|
|
12447
|
-
Dust
|
|
13010
|
+
Dust aims to be a minimal, low-overhead planning system that stays relevant over time.
|
|
12448
13011
|
|
|
12449
|
-
|
|
13012
|
+
Planning artifacts are simple markdown files that live alongside code. Ideas are intentionally vague until implementation is imminent. Tasks are small and completable in single commits. Facts document current reality rather than aspirational states.
|
|
12450
13013
|
|
|
12451
|
-
|
|
13014
|
+
The system avoids the staleness problem by deferring detail until the last responsible moment and deleting completed work rather than archiving it.
|
|
12452
13015
|
|
|
12453
13016
|
## Parent Principle
|
|
12454
13017
|
|
|
12455
|
-
- [
|
|
13018
|
+
- [Human-AI Collaboration](human-ai-collaboration.md)
|
|
12456
13019
|
|
|
12457
13020
|
## Sub-Principles
|
|
12458
13021
|
|
|
12459
|
-
- [
|
|
12460
|
-
- [
|
|
12461
|
-
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
12462
|
-
- [Lightweight Planning](lightweight-planning.md)
|
|
13022
|
+
- [Task-First Workflow](task-first-workflow.md)
|
|
13023
|
+
- [Some Big Design Up Front](some-big-design-up-front.md)
|
|
12463
13024
|
`
|
|
12464
13025
|
},
|
|
12465
13026
|
{
|
|
12466
|
-
slug: "
|
|
12467
|
-
content: `#
|
|
13027
|
+
slug: "easy-adoption",
|
|
13028
|
+
content: `# Easy Adoption
|
|
12468
13029
|
|
|
12469
|
-
Dust should
|
|
13030
|
+
Dust should be trivially easy to adopt in any repository.
|
|
12470
13031
|
|
|
12471
|
-
|
|
13032
|
+
Getting started with Dust should require minimal friction. A developer should be able to bootstrap Dust in their repository with a single command, without needing to install dependencies, configure build tools, or understand the internals.
|
|
13033
|
+
|
|
13034
|
+
This lowers the barrier to entry and encourages experimentation.
|
|
12472
13035
|
|
|
12473
13036
|
## Parent Principle
|
|
12474
13037
|
|
|
12475
|
-
- [
|
|
13038
|
+
- [Human-AI Collaboration](human-ai-collaboration.md)
|
|
12476
13039
|
|
|
12477
13040
|
## Sub-Principles
|
|
12478
13041
|
|
|
12479
|
-
- (
|
|
13042
|
+
- [Cross-Platform Compatibility](cross-platform-compatibility.md)
|
|
13043
|
+
- [Unsurprising UX](unsurprising-ux.md)
|
|
13044
|
+
- [VCS Independence](vcs-independence.md)
|
|
12480
13045
|
`
|
|
12481
13046
|
},
|
|
12482
13047
|
{
|
|
12483
|
-
slug: "
|
|
12484
|
-
content: `#
|
|
12485
|
-
|
|
12486
|
-
Tests must produce the same result regardless of where they run. A test that passes locally but fails in CI (or vice versa) is a broken test.
|
|
13048
|
+
slug: "intuitive-directory-structure",
|
|
13049
|
+
content: `# Intuitive Directory Structure
|
|
12487
13050
|
|
|
12488
|
-
|
|
12489
|
-
- Ambient environment variables (e.g. \`CLAUDECODE\`, \`CI\`, \`HOME\`)
|
|
12490
|
-
- The current working directory or filesystem layout of the host machine
|
|
12491
|
-
- Network availability or external services
|
|
12492
|
-
- The identity of the user or agent running the tests
|
|
13051
|
+
Code should be organized around related concerns in clearly named directories.
|
|
12493
13052
|
|
|
12494
|
-
When
|
|
13053
|
+
When files that serve similar purposes are grouped together, the codebase becomes easier to navigate and understand. A developer looking for "commands" should find them in a \`commands\` directory. Utilities should live with utilities. This organization reduces cognitive load and makes the project structure self-documenting.
|
|
12495
13054
|
|
|
12496
13055
|
## Parent Principle
|
|
12497
13056
|
|
|
12498
|
-
- [
|
|
13057
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
12499
13058
|
|
|
12500
13059
|
## Sub-Principles
|
|
12501
13060
|
|
|
12502
|
-
- (
|
|
13061
|
+
- [Co-located Tests](co-located-tests.md)
|
|
12503
13062
|
`
|
|
12504
13063
|
},
|
|
12505
13064
|
{
|
|
12506
|
-
slug: "
|
|
12507
|
-
content: `#
|
|
12508
|
-
|
|
12509
|
-
Agents need effective tools for diagnosing and fixing issues without manual intervention.
|
|
12510
|
-
|
|
12511
|
-
Traditional debugging relies on breakpoints, stepping through code, and interactive inspection — techniques that work well for humans but poorly for agents. Agents need debugging tools that produce readable, structured output: stack traces with context, diff-friendly error messages, reproducible test cases, and diagnostic commands that can be run non-interactively.
|
|
13065
|
+
slug: "lint-everything",
|
|
13066
|
+
content: `# Lint Everything
|
|
12512
13067
|
|
|
12513
|
-
|
|
13068
|
+
Prefer static analysis over runtime checks. Every error caught by a linter is an error that never reaches tests, and every error caught by tests is an error that never reaches production.
|
|
12514
13069
|
|
|
12515
|
-
|
|
13070
|
+
Lint markdown, lint types, lint formatting. If it can be checked statically, check it. Linters are fast, deterministic, and catch entire categories of bugs before code even runs.
|
|
12516
13071
|
|
|
12517
|
-
|
|
13072
|
+
This project lints:
|
|
13073
|
+
- TypeScript (type checking and style)
|
|
13074
|
+
- Markdown (broken links, required sections)
|
|
13075
|
+
- Task files (structure validation)
|
|
13076
|
+
- Principle hierarchy (parent/child consistency)
|
|
12518
13077
|
|
|
12519
13078
|
## Parent Principle
|
|
12520
13079
|
|
|
12521
|
-
- [
|
|
13080
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12522
13081
|
|
|
12523
13082
|
## Sub-Principles
|
|
12524
13083
|
|
|
12525
|
-
|
|
13084
|
+
(none)
|
|
12526
13085
|
`
|
|
12527
13086
|
},
|
|
12528
13087
|
{
|
|
12529
|
-
slug: "
|
|
12530
|
-
content: `#
|
|
13088
|
+
slug: "progressive-disclosure",
|
|
13089
|
+
content: `# Progressive Disclosure
|
|
12531
13090
|
|
|
12532
|
-
|
|
13091
|
+
Dust should reveal details progressively as a way of achieving context window efficiency.
|
|
12533
13092
|
|
|
12534
|
-
|
|
13093
|
+
Not all information is needed at once. A task list showing just titles is sufficient for choosing what to work on. Full task details are only needed when actively implementing. Linked principles and facts can be followed when deeper context is required.
|
|
12535
13094
|
|
|
12536
|
-
|
|
13095
|
+
This layered approach keeps initial reads lightweight while preserving access to complete information when needed.
|
|
12537
13096
|
|
|
12538
13097
|
## Parent Principle
|
|
12539
13098
|
|
|
12540
|
-
- [
|
|
13099
|
+
- [Context Window Efficiency](context-window-efficiency.md)
|
|
12541
13100
|
|
|
12542
13101
|
## Sub-Principles
|
|
12543
13102
|
|
|
12544
|
-
-
|
|
13103
|
+
- (none)
|
|
12545
13104
|
`
|
|
12546
13105
|
},
|
|
12547
13106
|
{
|
|
12548
|
-
slug: "
|
|
12549
|
-
content: `#
|
|
12550
|
-
|
|
12551
|
-
Dust is designed to support a non-branching workflow where developers commit directly to a single main branch.
|
|
12552
|
-
|
|
12553
|
-
In trunk-based development, teams collaborate on code in one primary branch rather than maintaining multiple long-lived feature branches. This eliminates merge conflicts, enables continuous integration, and keeps the codebase continuously releasable.
|
|
13107
|
+
slug: "context-optimised-code",
|
|
13108
|
+
content: `# Context-Optimised Code
|
|
12554
13109
|
|
|
12555
|
-
|
|
13110
|
+
Code should be structured so that agents can understand and modify it within their context window constraints.
|
|
12556
13111
|
|
|
12557
|
-
|
|
13112
|
+
Large files, deeply nested abstractions, and sprawling dependency chains all work against agents. A 3,000-line file cannot be fully loaded into context. A function that requires understanding six levels of indirection demands more context than one that is self-contained. Context-optimised code favours small files, shallow abstractions, explicit dependencies, and co-located related logic.
|
|
12558
13113
|
|
|
12559
|
-
|
|
13114
|
+
Dust should help projects identify files that are too large, modules that are too tangled, and patterns that make agent comprehension harder than it needs to be. This is not just about file size — it is about ensuring that the unit of code an agent needs to understand fits comfortably within the window available.
|
|
12560
13115
|
|
|
12561
13116
|
## Parent Principle
|
|
12562
13117
|
|
|
12563
|
-
- [
|
|
13118
|
+
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
12564
13119
|
|
|
12565
13120
|
## Sub-Principles
|
|
12566
13121
|
|
|
12567
|
-
(none)
|
|
13122
|
+
- (none)
|
|
12568
13123
|
`
|
|
12569
13124
|
},
|
|
12570
13125
|
{
|
|
12571
|
-
slug: "
|
|
12572
|
-
content: `#
|
|
12573
|
-
|
|
12574
|
-
Assert the whole, not the parts.
|
|
12575
|
-
|
|
12576
|
-
When you break a complex object into many small assertions, a failure tells you *one thing that's wrong*. When you assert against the whole expected value, the diff tells you *what actually happened versus what you expected* — the full picture, in one glance.
|
|
13126
|
+
slug: "some-big-design-up-front",
|
|
13127
|
+
content: `# Some Big Design Up Front
|
|
12577
13128
|
|
|
12578
|
-
|
|
13129
|
+
AI agents lower the cost of architectural exploration, making heavier upfront investment rational during the idea phase.
|
|
12579
13130
|
|
|
12580
|
-
|
|
13131
|
+
Agile's rejection of "big design up front" (BDUF) was largely economic: detailed architecture was expensive to produce and often wrong. AI agents change that equation — they can explore multiple variants, prototype them, and measure trade-offs cheaply. When evaluating alternatives costs less, the expected value of avoiding large structural mistakes increases.
|
|
12581
13132
|
|
|
12582
|
-
|
|
13133
|
+
This doesn't mean returning to traditional BDUF. Uncertainty about future requirements still limits what prediction can achieve. The insight is that the optimal amount of upfront work has shifted, not that prediction became reliable.
|
|
12583
13134
|
|
|
12584
|
-
|
|
12585
|
-
// Fragmented — each failure is a narrow keyhole
|
|
12586
|
-
expect(result.name).toBe("Alice");
|
|
12587
|
-
expect(result.age).toBe(30);
|
|
12588
|
-
expect(result.role).toBe("admin");
|
|
13135
|
+
The model is hybrid: thorough AI-assisted exploration during ideas, followed by straightforward execution during tasks. "Lightweight" refers to task-level planning, not idea-level exploration. Invest heavily in understanding alternatives during the idea phase, then decompose into atomic tasks once the direction is clear.
|
|
12589
13136
|
|
|
12590
|
-
|
|
12591
|
-
expect(result).toEqual({
|
|
12592
|
-
name: "Alice",
|
|
12593
|
-
age: 30,
|
|
12594
|
-
role: "admin",
|
|
12595
|
-
});
|
|
12596
|
-
\`\`\`
|
|
13137
|
+
## Convergence Criteria
|
|
12597
13138
|
|
|
12598
|
-
|
|
13139
|
+
Exploration should continue until clear trade-offs are identified and the chosen approach can be articulated against alternatives. This is convergence-based, not time-boxed — simple ideas converge quickly, complex architectural decisions require more exploration.
|
|
12599
13140
|
|
|
12600
|
-
|
|
13141
|
+
When exploration feels "done":
|
|
12601
13142
|
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
12605
|
-
|
|
13143
|
+
- Multiple approaches have been considered
|
|
13144
|
+
- Trade-offs between approaches are understood
|
|
13145
|
+
- The chosen direction has clear justification
|
|
13146
|
+
- Remaining uncertainty is about requirements, not design
|
|
12606
13147
|
|
|
12607
|
-
|
|
12608
|
-
expect(array).toEqual(['apples', 'oranges'])
|
|
12609
|
-
\`\`\`
|
|
13148
|
+
If a task requires significant design decisions during execution, it wasn't ready to be a task.
|
|
12610
13149
|
|
|
12611
|
-
##
|
|
13150
|
+
## Documenting Alternatives
|
|
12612
13151
|
|
|
12613
|
-
|
|
13152
|
+
Ideas should document the alternatives considered and why they were ruled out. This creates a decision log that helps future agents and humans understand context. Include alternatives in the idea body or Open Questions sections.
|
|
12614
13153
|
|
|
12615
13154
|
## Parent Principle
|
|
12616
13155
|
|
|
12617
|
-
- [
|
|
13156
|
+
- [Lightweight Planning](lightweight-planning.md)
|
|
12618
13157
|
|
|
12619
13158
|
## Sub-Principles
|
|
12620
13159
|
|
|
@@ -12622,22 +13161,16 @@ Work supports this principle when test failures tell a rich story — showing th
|
|
|
12622
13161
|
`
|
|
12623
13162
|
},
|
|
12624
13163
|
{
|
|
12625
|
-
slug: "
|
|
12626
|
-
content: `#
|
|
12627
|
-
|
|
12628
|
-
Dust should work consistently across operating systems: Linux, macOS, and Windows.
|
|
13164
|
+
slug: "traceable-decisions",
|
|
13165
|
+
content: `# Traceable Decisions
|
|
12629
13166
|
|
|
12630
|
-
|
|
12631
|
-
- Avoiding platform-specific shell commands or syntax
|
|
12632
|
-
- Using cross-platform path handling
|
|
12633
|
-
- Testing on multiple platforms when possible
|
|
12634
|
-
- Documenting any platform-specific limitations
|
|
13167
|
+
The commit history should explain why changes were made, not just what changed.
|
|
12635
13168
|
|
|
12636
|
-
|
|
13169
|
+
Commit messages should capture intent and context that would otherwise be lost. Future maintainers (human or AI) will traverse history to understand the reasoning behind decisions. A commit that says "Fix bug" is less valuable than one that explains what was broken and why the fix is correct.
|
|
12637
13170
|
|
|
12638
13171
|
## Parent Principle
|
|
12639
13172
|
|
|
12640
|
-
- [
|
|
13173
|
+
- [Atomic Commits](atomic-commits.md)
|
|
12641
13174
|
|
|
12642
13175
|
## Sub-Principles
|
|
12643
13176
|
|
|
@@ -12645,22 +13178,16 @@ Cross-platform support broadens adoption and ensures teams with mixed environmen
|
|
|
12645
13178
|
`
|
|
12646
13179
|
},
|
|
12647
13180
|
{
|
|
12648
|
-
slug: "
|
|
12649
|
-
content: `#
|
|
12650
|
-
|
|
12651
|
-
Agents need tools to efficiently explore and understand unfamiliar codebases.
|
|
12652
|
-
|
|
12653
|
-
When an agent encounters a new codebase — or an unfamiliar corner of a familiar one — it needs to quickly build a mental model: what exists, how it fits together, and where to make changes. Without good exploratory tools, agents waste context on trial-and-error searches, reading irrelevant files, and forming incorrect assumptions.
|
|
12654
|
-
|
|
12655
|
-
Dust should promote and integrate tools that help agents explore: dependency graphs, module overviews, search utilities tuned for code navigation, and summaries of project structure. The goal is to make the "orientation" phase of any task as short and reliable as possible.
|
|
13181
|
+
slug: "fast-feedback",
|
|
13182
|
+
content: `# Fast Feedback
|
|
12656
13183
|
|
|
12657
|
-
|
|
13184
|
+
Dust should provide fast feedback loops for developers.
|
|
12658
13185
|
|
|
12659
|
-
|
|
13186
|
+
Scripts and tooling should execute quickly so developers can iterate rapidly. Slow feedback discourages frequent validation and leads to larger, riskier changes. Fast feedback enables small, confident steps.
|
|
12660
13187
|
|
|
12661
13188
|
## Parent Principle
|
|
12662
13189
|
|
|
12663
|
-
- [
|
|
13190
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12664
13191
|
|
|
12665
13192
|
## Sub-Principles
|
|
12666
13193
|
|
|
@@ -12668,50 +13195,63 @@ Internal
|
|
|
12668
13195
|
`
|
|
12669
13196
|
},
|
|
12670
13197
|
{
|
|
12671
|
-
slug: "
|
|
12672
|
-
content: `#
|
|
13198
|
+
slug: "decoupled-code",
|
|
13199
|
+
content: `# Decoupled Code
|
|
12673
13200
|
|
|
12674
|
-
|
|
13201
|
+
Code should be organized into independent units with explicit dependencies.
|
|
12675
13202
|
|
|
12676
|
-
|
|
13203
|
+
Decoupled code is easier to test, understand, and modify. Dependencies are passed in rather than hard-coded, enabling units to be tested in isolation and composed flexibly. This reduces the blast radius of changes and makes the system more maintainable.
|
|
12677
13204
|
|
|
12678
13205
|
## Parent Principle
|
|
12679
13206
|
|
|
12680
|
-
- [
|
|
13207
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12681
13208
|
|
|
12682
13209
|
## Sub-Principles
|
|
12683
13210
|
|
|
12684
|
-
- (
|
|
13211
|
+
- [Dependency Injection](dependency-injection.md)
|
|
13212
|
+
- [Stubs Over Mocks](stubs-over-mocks.md)
|
|
13213
|
+
- [Functional Core, Imperative Shell](functional-core-imperative-shell.md)
|
|
13214
|
+
- [Design for Testability](design-for-testability.md)
|
|
12685
13215
|
`
|
|
12686
13216
|
},
|
|
12687
13217
|
{
|
|
12688
|
-
slug: "
|
|
12689
|
-
content: `#
|
|
13218
|
+
slug: "make-changes-with-confidence",
|
|
13219
|
+
content: `# Make Changes with Confidence
|
|
12690
13220
|
|
|
12691
|
-
|
|
13221
|
+
Developers should be able to modify code without fear of breaking existing behavior.
|
|
12692
13222
|
|
|
12693
|
-
Tests
|
|
13223
|
+
Tests, type checking, and other automated verification enable safe refactoring and evolution of the codebase. When changes break something, fast feedback identifies the problem before it spreads. This confidence encourages continuous improvement rather than fragile, stagnant code.
|
|
12694
13224
|
|
|
12695
13225
|
## Parent Principle
|
|
12696
13226
|
|
|
12697
|
-
- [
|
|
13227
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
12698
13228
|
|
|
12699
13229
|
## Sub-Principles
|
|
12700
13230
|
|
|
12701
|
-
- (
|
|
13231
|
+
- [Comprehensive Assertions](comprehensive-assertions.md)
|
|
13232
|
+
- [Decoupled Code](decoupled-code.md)
|
|
13233
|
+
- [Fast Feedback](fast-feedback.md)
|
|
13234
|
+
- [Lint Everything](lint-everything.md)
|
|
13235
|
+
- [Readable Test Data](readable-test-data.md)
|
|
13236
|
+
- [Reproducible Checks](reproducible-checks.md)
|
|
13237
|
+
- [Stop the Line](stop-the-line.md)
|
|
13238
|
+
- [Keep Unit Tests Pure](keep-unit-tests-pure.md)
|
|
13239
|
+
- [Test Isolation](test-isolation.md)
|
|
13240
|
+
- [Self-Diagnosing Tests](self-diagnosing-tests.md)
|
|
13241
|
+
- [Unit Test Coverage](unit-test-coverage.md)
|
|
12702
13242
|
`
|
|
12703
13243
|
},
|
|
12704
13244
|
{
|
|
12705
|
-
slug: "
|
|
12706
|
-
content: `#
|
|
13245
|
+
slug: "clarity-over-brevity",
|
|
13246
|
+
content: `# Clarity Over Brevity
|
|
12707
13247
|
|
|
12708
|
-
|
|
13248
|
+
Names should be descriptive and self-documenting, even if longer.
|
|
12709
13249
|
|
|
12710
|
-
|
|
13250
|
+
Abbreviated names like \`ctx\`, \`deps\`, \`fs\`, or \`args\` save a few keystrokes but obscure meaning. Full names like \`context\`, \`dependencies\`, \`fileSystem\`, and \`arguments\` make code immediately understandable without requiring readers to decode conventions. This is especially valuable when AI agents or new contributors read the codebase for the first time.
|
|
12711
13251
|
|
|
12712
13252
|
## Parent Principle
|
|
12713
13253
|
|
|
12714
|
-
- [
|
|
13254
|
+
- [Naming Matters](naming-matters.md)
|
|
12715
13255
|
|
|
12716
13256
|
## Sub-Principles
|
|
12717
13257
|
|
|
@@ -12719,58 +13259,76 @@ This discipline ensures that every change has a documented purpose. The commit h
|
|
|
12719
13259
|
`
|
|
12720
13260
|
},
|
|
12721
13261
|
{
|
|
12722
|
-
slug: "agent-
|
|
12723
|
-
content: `# Agent
|
|
13262
|
+
slug: "agent-agnostic-design",
|
|
13263
|
+
content: `# Agent-Agnostic Design
|
|
12724
13264
|
|
|
12725
|
-
Dust
|
|
13265
|
+
Dust should work with multiple agents without favoring one.
|
|
12726
13266
|
|
|
12727
|
-
|
|
13267
|
+
Rather than implementing agents, Dust generates prompts and context that can be passed to any capable agent. This keeps Dust lightweight and allows teams to use whatever agent tooling they prefer.
|
|
13268
|
+
|
|
13269
|
+
Dust may have built-in support for invoking popular agents (Claude, Aider, Codex, etc.), but the choice of agent should always be made by the user at runtime - never hard-coded into repository configuration.
|
|
13270
|
+
|
|
13271
|
+
Note: Supporting multiple agents directly contributes to [Easy Adoption](easy-adoption.md), since teams can use their preferred agent tools without being locked into a specific platform.
|
|
13272
|
+
|
|
13273
|
+
## Applicability
|
|
13274
|
+
|
|
13275
|
+
Internal
|
|
12728
13276
|
|
|
12729
13277
|
## Parent Principle
|
|
12730
13278
|
|
|
12731
|
-
- [
|
|
13279
|
+
- [Agent Autonomy](agent-autonomy.md)
|
|
12732
13280
|
|
|
12733
13281
|
## Sub-Principles
|
|
12734
13282
|
|
|
12735
|
-
-
|
|
12736
|
-
- [Batteries Included](batteries-included.md)
|
|
12737
|
-
- [Agent-Agnostic Design](agent-agnostic-design.md)
|
|
12738
|
-
- [Agent Context Inference](agent-context-inference.md)
|
|
12739
|
-
- [Agent-Specific Enhancement](agent-specific-enhancement.md)
|
|
12740
|
-
- [Context Window Efficiency](context-window-efficiency.md)
|
|
12741
|
-
- [Small Units](small-units.md)
|
|
13283
|
+
- (none)
|
|
12742
13284
|
`
|
|
12743
13285
|
},
|
|
12744
13286
|
{
|
|
12745
|
-
slug: "
|
|
12746
|
-
content: `#
|
|
13287
|
+
slug: "readable-test-data",
|
|
13288
|
+
content: `# Readable Test Data
|
|
12747
13289
|
|
|
12748
|
-
|
|
13290
|
+
Test data setup should use natural structures that mirror what they represent.
|
|
12749
13291
|
|
|
12750
|
-
|
|
13292
|
+
## Why it matters
|
|
12751
13293
|
|
|
12752
|
-
|
|
13294
|
+
When test data is easy to read, tests become self-documenting. A file system hierarchy expressed as a nested object immediately conveys structure, while a flat Map with path strings requires mental parsing to understand the relationships.
|
|
12753
13295
|
|
|
12754
|
-
|
|
13296
|
+
## In practice
|
|
12755
13297
|
|
|
12756
|
-
|
|
13298
|
+
Prefer literal structures that visually match the domain:
|
|
12757
13299
|
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12761
|
-
|
|
12762
|
-
|
|
12763
|
-
|
|
13300
|
+
\`\`\`javascript
|
|
13301
|
+
// Avoid: flat paths that obscure hierarchy
|
|
13302
|
+
const fs = createFileSystemEmulator({
|
|
13303
|
+
files: new Map([['/project/.dust/principles/my-goal.md', '# My Goal']]),
|
|
13304
|
+
existingPaths: new Set(['/project/.dust/ideas']),
|
|
13305
|
+
})
|
|
12764
13306
|
|
|
12765
|
-
|
|
13307
|
+
// Prefer: nested object that mirrors file system structure
|
|
13308
|
+
const fs = createFileSystemEmulator({
|
|
13309
|
+
project: {
|
|
13310
|
+
'.dust': {
|
|
13311
|
+
principles: {
|
|
13312
|
+
'my-goal.md': '# My Goal'
|
|
13313
|
+
},
|
|
13314
|
+
ideas: {}
|
|
13315
|
+
}
|
|
13316
|
+
}
|
|
13317
|
+
})
|
|
13318
|
+
\`\`\`
|
|
13319
|
+
|
|
13320
|
+
The nested form:
|
|
13321
|
+
- Shows parent-child relationships through indentation
|
|
13322
|
+
- Makes empty directories explicit with empty objects
|
|
13323
|
+
- Requires no mental path concatenation to understand structure
|
|
12766
13324
|
|
|
12767
|
-
|
|
13325
|
+
## How to evaluate
|
|
12768
13326
|
|
|
12769
|
-
|
|
13327
|
+
Work supports this principle when test setup data uses structures that visually resemble what they represent, reducing cognitive load for readers.
|
|
12770
13328
|
|
|
12771
13329
|
## Parent Principle
|
|
12772
13330
|
|
|
12773
|
-
- [
|
|
13331
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12774
13332
|
|
|
12775
13333
|
## Sub-Principles
|
|
12776
13334
|
|
|
@@ -12778,14 +13336,12 @@ Dust should help projects measure the speed of their feedback loops, identify bo
|
|
|
12778
13336
|
`
|
|
12779
13337
|
},
|
|
12780
13338
|
{
|
|
12781
|
-
slug: "
|
|
12782
|
-
content: `#
|
|
12783
|
-
|
|
12784
|
-
For each desired change, make the change easy, then make the easy change.
|
|
13339
|
+
slug: "reasonably-dry",
|
|
13340
|
+
content: `# Reasonably DRY
|
|
12785
13341
|
|
|
12786
|
-
|
|
13342
|
+
Don't repeat yourself is a good principle, but don't overdo it.
|
|
12787
13343
|
|
|
12788
|
-
|
|
13344
|
+
Extracting shared code too eagerly can create tight coupling, obscure intent, and make changes harder. When two pieces of code look similar but serve different purposes or are likely to evolve independently, duplication is the better choice. The cost of a wrong abstraction is higher than the cost of a little repetition. Extract shared code when the duplication is truly about the same concept and has proven stable, not just because two things happen to look alike right now.
|
|
12789
13345
|
|
|
12790
13346
|
## Parent Principle
|
|
12791
13347
|
|
|
@@ -12797,20 +13353,21 @@ Work that supports this principle includes refactoring before feature work, impr
|
|
|
12797
13353
|
`
|
|
12798
13354
|
},
|
|
12799
13355
|
{
|
|
12800
|
-
slug: "
|
|
12801
|
-
content: `#
|
|
12802
|
-
|
|
12803
|
-
Where possible, developers and agents should have everything they need to be productive, within the repository.
|
|
13356
|
+
slug: "actionable-errors",
|
|
13357
|
+
content: `# Actionable Errors
|
|
12804
13358
|
|
|
12805
|
-
|
|
13359
|
+
Error messages should tell you what to do next, not just what went wrong.
|
|
12806
13360
|
|
|
12807
|
-
|
|
13361
|
+
When something fails, the message should provide:
|
|
13362
|
+
- A clear description of the problem
|
|
13363
|
+
- Specific guidance on how to fix it
|
|
13364
|
+
- Context needed to take the next step
|
|
12808
13365
|
|
|
12809
|
-
|
|
13366
|
+
This is especially important for AI agents, who need concrete instructions to recover autonomously. A good error message turns a dead end into a signpost.
|
|
12810
13367
|
|
|
12811
13368
|
## Parent Principle
|
|
12812
13369
|
|
|
12813
|
-
- [
|
|
13370
|
+
- [Agent Autonomy](agent-autonomy.md)
|
|
12814
13371
|
|
|
12815
13372
|
## Sub-Principles
|
|
12816
13373
|
|
|
@@ -12818,16 +13375,18 @@ Internal
|
|
|
12818
13375
|
`
|
|
12819
13376
|
},
|
|
12820
13377
|
{
|
|
12821
|
-
slug: "
|
|
12822
|
-
content: `#
|
|
13378
|
+
slug: "make-the-change-easy",
|
|
13379
|
+
content: `# Make the Change Easy
|
|
12823
13380
|
|
|
12824
|
-
|
|
13381
|
+
For each desired change, make the change easy, then make the easy change.
|
|
12825
13382
|
|
|
12826
|
-
|
|
13383
|
+
This principle, articulated by Kent Beck, recognizes that the hardest part of a change is often not the change itself but the state of the code receiving it. When code resists a change, the right response is to first refactor until the change becomes straightforward, and only then make it. The warning - "this may be hard" - acknowledges that preparing the ground takes real effort, but the result is a change that fits naturally rather than one forced in against the grain.
|
|
13384
|
+
|
|
13385
|
+
Work that supports this principle includes refactoring before feature work, improving abstractions that make a category of changes simpler, and resisting the urge to bolt changes onto code that isn't ready for them.
|
|
12827
13386
|
|
|
12828
13387
|
## Parent Principle
|
|
12829
13388
|
|
|
12830
|
-
- [
|
|
13389
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
12831
13390
|
|
|
12832
13391
|
## Sub-Principles
|
|
12833
13392
|
|
|
@@ -12835,16 +13394,18 @@ Commit messages should capture intent and context that would otherwise be lost.
|
|
|
12835
13394
|
`
|
|
12836
13395
|
},
|
|
12837
13396
|
{
|
|
12838
|
-
slug: "
|
|
12839
|
-
content: `#
|
|
13397
|
+
slug: "dependency-injection",
|
|
13398
|
+
content: `# Dependency Injection
|
|
12840
13399
|
|
|
12841
|
-
|
|
13400
|
+
Avoid global mocks. Dependency injection is almost always preferable to testing code that depends directly on globals.
|
|
12842
13401
|
|
|
12843
|
-
|
|
13402
|
+
When code depends on global state or singletons, testing requires mocking those globals—which introduces hidden coupling, complicates test setup, and risks interference between tests. Dependency injection makes dependencies explicit: they're passed in as arguments, making the code's requirements visible and enabling tests to supply controlled implementations.
|
|
13403
|
+
|
|
13404
|
+
This approach improves testability (each test controls its own dependencies), readability (dependencies are declared upfront), and flexibility (swapping implementations doesn't require changing the consuming code). It also makes refactoring safer since dependencies are explicit rather than implicit.
|
|
12844
13405
|
|
|
12845
13406
|
## Parent Principle
|
|
12846
13407
|
|
|
12847
|
-
- [
|
|
13408
|
+
- [Decoupled Code](decoupled-code.md)
|
|
12848
13409
|
|
|
12849
13410
|
## Sub-Principles
|
|
12850
13411
|
|
|
@@ -12852,84 +13413,53 @@ Excluding system tests from coverage reporting focuses attention on unit tests -
|
|
|
12852
13413
|
`
|
|
12853
13414
|
},
|
|
12854
13415
|
{
|
|
12855
|
-
slug: "
|
|
12856
|
-
content: `#
|
|
13416
|
+
slug: "repository-hygiene",
|
|
13417
|
+
content: `# Repository Hygiene
|
|
12857
13418
|
|
|
12858
|
-
|
|
13419
|
+
Dust repositories should maintain a clean, organized state with minimal noise.
|
|
12859
13420
|
|
|
12860
|
-
|
|
13421
|
+
This includes proper gitignore configuration to exclude build artifacts, dependencies, editor files, and other generated content from version control. A well-maintained repository makes it easier for both humans and AI to navigate and understand the codebase.
|
|
12861
13422
|
|
|
12862
13423
|
## Parent Principle
|
|
12863
13424
|
|
|
12864
|
-
- [
|
|
13425
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
12865
13426
|
|
|
12866
13427
|
## Sub-Principles
|
|
12867
13428
|
|
|
12868
|
-
- [
|
|
12869
|
-
- [
|
|
12870
|
-
- [Functional Core, Imperative Shell](functional-core-imperative-shell.md)
|
|
12871
|
-
- [Design for Testability](design-for-testability.md)
|
|
13429
|
+
- [Atomic Commits](atomic-commits.md)
|
|
13430
|
+
- [Trunk-Based Development](trunk-based-development.md)
|
|
12872
13431
|
`
|
|
12873
13432
|
},
|
|
12874
13433
|
{
|
|
12875
|
-
slug: "
|
|
12876
|
-
content: `#
|
|
12877
|
-
|
|
12878
|
-
Prefer static analysis over runtime checks. Every error caught by a linter is an error that never reaches tests, and every error caught by tests is an error that never reaches production.
|
|
12879
|
-
|
|
12880
|
-
Lint markdown, lint types, lint formatting. If it can be checked statically, check it. Linters are fast, deterministic, and catch entire categories of bugs before code even runs.
|
|
12881
|
-
|
|
12882
|
-
This project lints:
|
|
12883
|
-
- TypeScript (type checking and style)
|
|
12884
|
-
- Markdown (broken links, required sections)
|
|
12885
|
-
- Task files (structure validation)
|
|
12886
|
-
- Principle hierarchy (parent/child consistency)
|
|
12887
|
-
|
|
12888
|
-
## Parent Principle
|
|
13434
|
+
slug: "batteries-included",
|
|
13435
|
+
content: `# Batteries Included
|
|
12889
13436
|
|
|
12890
|
-
|
|
13437
|
+
Dust should provide everything that is required (within reason) for an agent to be productive in an arbitrary codebase.
|
|
12891
13438
|
|
|
12892
|
-
|
|
13439
|
+
An agent working autonomously should not be blocked because a tool or configuration is missing. For example, dust should ship custom lint rules for different linters, even though those linters are not dependencies of dust itself. If an agent needs a capability to do its job well in a typical codebase, dust should provide it out of the box.
|
|
12893
13440
|
|
|
12894
|
-
|
|
12895
|
-
`
|
|
12896
|
-
},
|
|
12897
|
-
{
|
|
12898
|
-
slug: "maintainable-codebase",
|
|
12899
|
-
content: `# Maintainable Codebase
|
|
13441
|
+
This means accepting some breadth of scope — bundling configs, rules, and utilities that target external tools — in exchange for agents that can start producing useful work immediately without manual setup.
|
|
12900
13442
|
|
|
12901
|
-
|
|
13443
|
+
## Applicability
|
|
12902
13444
|
|
|
12903
|
-
|
|
13445
|
+
Internal
|
|
12904
13446
|
|
|
12905
13447
|
## Parent Principle
|
|
12906
13448
|
|
|
12907
|
-
- [
|
|
13449
|
+
- [Agent Autonomy](agent-autonomy.md)
|
|
12908
13450
|
|
|
12909
13451
|
## Sub-Principles
|
|
12910
|
-
|
|
12911
|
-
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
12912
|
-
- [Minimal Dependencies](minimal-dependencies.md)
|
|
12913
|
-
- [Intuitive Directory Structure](intuitive-directory-structure.md)
|
|
12914
|
-
- [Repository Hygiene](repository-hygiene.md)
|
|
12915
|
-
- [Naming Matters](naming-matters.md)
|
|
12916
|
-
- [Reasonably DRY](reasonably-dry.md)
|
|
12917
|
-
- [Make the Change Easy](make-the-change-easy.md)
|
|
12918
|
-
- [Boy Scout Rule](boy-scout-rule.md)
|
|
12919
|
-
- [Broken Windows](broken-windows.md)
|
|
12920
13452
|
`
|
|
12921
13453
|
},
|
|
12922
13454
|
{
|
|
12923
|
-
slug: "
|
|
12924
|
-
content: `#
|
|
12925
|
-
|
|
12926
|
-
Dust should work with multiple agents without favoring one.
|
|
13455
|
+
slug: "development-traceability",
|
|
13456
|
+
content: `# Development Traceability
|
|
12927
13457
|
|
|
12928
|
-
|
|
13458
|
+
Structured logging and tracing help agents understand system behaviour without resorting to ad-hoc testing cycles.
|
|
12929
13459
|
|
|
12930
|
-
|
|
13460
|
+
When something goes wrong, agents often resort to adding temporary log statements, running the code, reading the output, and repeating — a slow and wasteful debugging loop. Good traceability means the system already records what happened and why, through structured logs, trace IDs, and observable state. This lets agents diagnose issues by reading existing output rather than generating new experiments.
|
|
12931
13461
|
|
|
12932
|
-
|
|
13462
|
+
Dust should encourage projects to adopt structured logging, promote traceability as a first-class concern, and provide tools that surface relevant trace information when agents need it.
|
|
12933
13463
|
|
|
12934
13464
|
## Applicability
|
|
12935
13465
|
|
|
@@ -12937,7 +13467,7 @@ Internal
|
|
|
12937
13467
|
|
|
12938
13468
|
## Parent Principle
|
|
12939
13469
|
|
|
12940
|
-
- [Agent
|
|
13470
|
+
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
12941
13471
|
|
|
12942
13472
|
## Sub-Principles
|
|
12943
13473
|
|
|
@@ -12945,42 +13475,22 @@ Internal
|
|
|
12945
13475
|
`
|
|
12946
13476
|
},
|
|
12947
13477
|
{
|
|
12948
|
-
slug: "
|
|
12949
|
-
content: `#
|
|
12950
|
-
|
|
12951
|
-
Dust should be trivially easy to adopt in any repository.
|
|
12952
|
-
|
|
12953
|
-
Getting started with Dust should require minimal friction. A developer should be able to bootstrap Dust in their repository with a single command, without needing to install dependencies, configure build tools, or understand the internals.
|
|
12954
|
-
|
|
12955
|
-
This lowers the barrier to entry and encourages experimentation.
|
|
12956
|
-
|
|
12957
|
-
## Parent Principle
|
|
12958
|
-
|
|
12959
|
-
- [Human-AI Collaboration](human-ai-collaboration.md)
|
|
13478
|
+
slug: "exploratory-tooling",
|
|
13479
|
+
content: `# Exploratory Tooling
|
|
12960
13480
|
|
|
12961
|
-
|
|
13481
|
+
Agents need tools to efficiently explore and understand unfamiliar codebases.
|
|
12962
13482
|
|
|
12963
|
-
|
|
12964
|
-
- [Unsurprising UX](unsurprising-ux.md)
|
|
12965
|
-
- [VCS Independence](vcs-independence.md)
|
|
12966
|
-
`
|
|
12967
|
-
},
|
|
12968
|
-
{
|
|
12969
|
-
slug: "actionable-errors",
|
|
12970
|
-
content: `# Actionable Errors
|
|
13483
|
+
When an agent encounters a new codebase — or an unfamiliar corner of a familiar one — it needs to quickly build a mental model: what exists, how it fits together, and where to make changes. Without good exploratory tools, agents waste context on trial-and-error searches, reading irrelevant files, and forming incorrect assumptions.
|
|
12971
13484
|
|
|
12972
|
-
|
|
13485
|
+
Dust should promote and integrate tools that help agents explore: dependency graphs, module overviews, search utilities tuned for code navigation, and summaries of project structure. The goal is to make the "orientation" phase of any task as short and reliable as possible.
|
|
12973
13486
|
|
|
12974
|
-
|
|
12975
|
-
- A clear description of the problem
|
|
12976
|
-
- Specific guidance on how to fix it
|
|
12977
|
-
- Context needed to take the next step
|
|
13487
|
+
## Applicability
|
|
12978
13488
|
|
|
12979
|
-
|
|
13489
|
+
Internal
|
|
12980
13490
|
|
|
12981
13491
|
## Parent Principle
|
|
12982
13492
|
|
|
12983
|
-
- [Agent
|
|
13493
|
+
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
12984
13494
|
|
|
12985
13495
|
## Sub-Principles
|
|
12986
13496
|
|
|
@@ -12988,71 +13498,57 @@ This is especially important for AI agents, who need concrete instructions to re
|
|
|
12988
13498
|
`
|
|
12989
13499
|
},
|
|
12990
13500
|
{
|
|
12991
|
-
slug: "
|
|
12992
|
-
content: `#
|
|
12993
|
-
|
|
12994
|
-
Names should follow established conventions within each category to reduce cognitive load.
|
|
12995
|
-
|
|
12996
|
-
Principles use Title Case. File names use kebab-case. Commands use lowercase with hyphens. When naming conventions exist, follow them. When they don't, establish one and apply it consistently. Inconsistent naming creates friction for both humans and AI agents trying to predict or recall identifiers.
|
|
12997
|
-
|
|
12998
|
-
## Parent Principle
|
|
12999
|
-
|
|
13000
|
-
- [Naming Matters](naming-matters.md)
|
|
13501
|
+
slug: "small-units",
|
|
13502
|
+
content: `# Small Units
|
|
13001
13503
|
|
|
13002
|
-
|
|
13504
|
+
Ideas, principles, facts, and tasks should each be as discrete and fine-grained as possible.
|
|
13003
13505
|
|
|
13004
|
-
|
|
13005
|
-
`
|
|
13006
|
-
},
|
|
13007
|
-
{
|
|
13008
|
-
slug: "minimal-dependencies",
|
|
13009
|
-
content: `# Minimal Dependencies
|
|
13506
|
+
Small, focused documents enable precise relationships between them. A task can link to exactly the principles it serves. A fact can describe one specific aspect of the system. This granularity reduces ambiguity.
|
|
13010
13507
|
|
|
13011
|
-
|
|
13508
|
+
Tasks especially benefit from being small. A narrowly scoped task gives agents or humans the best chance of delivering exactly what was intended, in a single atomic commit.
|
|
13012
13509
|
|
|
13013
|
-
|
|
13510
|
+
Note: This principle directly supports [Lightweight Planning](lightweight-planning.md), which explicitly mentions that "Tasks are small and completable in single commits."
|
|
13014
13511
|
|
|
13015
13512
|
## Parent Principle
|
|
13016
13513
|
|
|
13017
|
-
- [
|
|
13514
|
+
- [Agent Autonomy](agent-autonomy.md)
|
|
13018
13515
|
|
|
13019
13516
|
## Sub-Principles
|
|
13020
13517
|
|
|
13021
|
-
-
|
|
13518
|
+
- (none)
|
|
13022
13519
|
`
|
|
13023
13520
|
},
|
|
13024
13521
|
{
|
|
13025
|
-
slug: "
|
|
13026
|
-
content: `#
|
|
13027
|
-
|
|
13028
|
-
Dust should be designed with short attention spans in mind.
|
|
13522
|
+
slug: "naming-matters",
|
|
13523
|
+
content: `# Naming Matters
|
|
13029
13524
|
|
|
13030
|
-
|
|
13525
|
+
Good naming reduces waste by eliminating confusion and making code self-documenting.
|
|
13031
13526
|
|
|
13032
|
-
|
|
13527
|
+
Poor names cause rework, bugs, and communication overhead. When names don't clearly convey meaning, developers waste time deciphering code, misunderstand intentions, and introduce defects. Well-chosen names serve as documentation that never goes stale, reducing the need for explanatory comments and enabling both humans and AI agents to navigate the codebase efficiently.
|
|
13033
13528
|
|
|
13034
13529
|
## Parent Principle
|
|
13035
13530
|
|
|
13036
|
-
- [
|
|
13531
|
+
- [Maintainable Codebase](maintainable-codebase.md)
|
|
13037
13532
|
|
|
13038
13533
|
## Sub-Principles
|
|
13039
13534
|
|
|
13040
|
-
- [
|
|
13535
|
+
- [Consistent Naming](consistent-naming.md)
|
|
13536
|
+
- [Clarity Over Brevity](clarity-over-brevity.md)
|
|
13041
13537
|
`
|
|
13042
13538
|
},
|
|
13043
13539
|
{
|
|
13044
|
-
slug: "
|
|
13045
|
-
content: `#
|
|
13540
|
+
slug: "comprehensive-test-coverage",
|
|
13541
|
+
content: `# Comprehensive Test Coverage
|
|
13046
13542
|
|
|
13047
|
-
|
|
13543
|
+
A project's test suite is its primary safety net, and agents depend on it even more than humans do.
|
|
13048
13544
|
|
|
13049
|
-
|
|
13545
|
+
Agents cannot manually verify that their changes work. They rely entirely on automated tests to confirm correctness. Gaps in test coverage become gaps in agent capability — areas where changes are risky and feedback is absent. Comprehensive coverage means every meaningful behaviour is tested, so agents can make changes anywhere in the codebase with confidence.
|
|
13050
13546
|
|
|
13051
|
-
|
|
13547
|
+
Dust should help projects measure and improve their test coverage, flag untested areas, and encourage a culture where new code comes with new tests.
|
|
13052
13548
|
|
|
13053
13549
|
## Parent Principle
|
|
13054
13550
|
|
|
13055
|
-
- [
|
|
13551
|
+
- [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
|
|
13056
13552
|
|
|
13057
13553
|
## Sub-Principles
|
|
13058
13554
|
|
|
@@ -13060,18 +13556,16 @@ The Boy Scout Rule is not a license for large-scale refactoring during unrelated
|
|
|
13060
13556
|
`
|
|
13061
13557
|
},
|
|
13062
13558
|
{
|
|
13063
|
-
slug: "
|
|
13064
|
-
content: `#
|
|
13065
|
-
|
|
13066
|
-
The user interface should be as "guessable" as possible.
|
|
13559
|
+
slug: "stop-the-line",
|
|
13560
|
+
content: `# Stop the Line
|
|
13067
13561
|
|
|
13068
|
-
|
|
13562
|
+
Any worker — human or agent — should halt and fix a problem the moment they detect it, rather than letting defects propagate downstream.
|
|
13069
13563
|
|
|
13070
|
-
|
|
13564
|
+
Originating from the Toyota production system, "Stop the Line" empowers every participant to pause work immediately upon identifying a defect, failing check, or safety hazard. Problems are cheaper to fix at their source than after they've compounded through later stages. In the context of dust, this means agents and humans alike should treat broken checks, test failures, and lint errors as blockers that demand immediate attention — not warnings to be deferred.
|
|
13071
13565
|
|
|
13072
13566
|
## Parent Principle
|
|
13073
13567
|
|
|
13074
|
-
- [
|
|
13568
|
+
- [Make Changes with Confidence](make-changes-with-confidence.md)
|
|
13075
13569
|
|
|
13076
13570
|
## Sub-Principles
|
|
13077
13571
|
|
|
@@ -13081,10 +13575,10 @@ Surprising behavior erodes trust and slows people down. Unsurprising behavior le
|
|
|
13081
13575
|
];
|
|
13082
13576
|
|
|
13083
13577
|
// lib/artifacts/core-principles.ts
|
|
13084
|
-
function
|
|
13578
|
+
function sortNodes2(nodes) {
|
|
13085
13579
|
nodes.sort((a, b) => a.title.localeCompare(b.title));
|
|
13086
13580
|
for (const node of nodes) {
|
|
13087
|
-
|
|
13581
|
+
sortNodes2(node.children);
|
|
13088
13582
|
}
|
|
13089
13583
|
}
|
|
13090
13584
|
function isInternalPrinciple(principleContent) {
|
|
@@ -13128,12 +13622,12 @@ function getCorePrincipleTree(allPrinciples, config) {
|
|
|
13128
13622
|
nodeBySlug.get(parentSlug).children.push(node);
|
|
13129
13623
|
}
|
|
13130
13624
|
}
|
|
13131
|
-
|
|
13625
|
+
sortNodes2(roots);
|
|
13132
13626
|
return roots;
|
|
13133
13627
|
}
|
|
13134
13628
|
|
|
13135
13629
|
// lib/core-principles.ts
|
|
13136
|
-
function
|
|
13630
|
+
function extractLinksFromSection3(content, sectionHeading) {
|
|
13137
13631
|
const lines = content.split(`
|
|
13138
13632
|
`);
|
|
13139
13633
|
const links = [];
|
|
@@ -13158,8 +13652,8 @@ function extractLinksFromSection(content, sectionHeading) {
|
|
|
13158
13652
|
}
|
|
13159
13653
|
return links;
|
|
13160
13654
|
}
|
|
13161
|
-
function
|
|
13162
|
-
const links =
|
|
13655
|
+
function extractSingleLinkFromSection2(content, sectionHeading) {
|
|
13656
|
+
const links = extractLinksFromSection3(content, sectionHeading);
|
|
13163
13657
|
return links.length === 1 ? links[0] : null;
|
|
13164
13658
|
}
|
|
13165
13659
|
function parsePrincipleContent(slug, content) {
|
|
@@ -13167,8 +13661,8 @@ function parsePrincipleContent(slug, content) {
|
|
|
13167
13661
|
if (!title) {
|
|
13168
13662
|
throw new Error(`Principle has no title: ${slug}`);
|
|
13169
13663
|
}
|
|
13170
|
-
const parentPrinciple =
|
|
13171
|
-
const subPrinciples =
|
|
13664
|
+
const parentPrinciple = extractSingleLinkFromSection2(content, "Parent Principle");
|
|
13665
|
+
const subPrinciples = extractLinksFromSection3(content, "Sub-Principles");
|
|
13172
13666
|
return {
|
|
13173
13667
|
slug,
|
|
13174
13668
|
title,
|
|
@@ -13377,7 +13871,6 @@ async function init(dependencies) {
|
|
|
13377
13871
|
}
|
|
13378
13872
|
|
|
13379
13873
|
// lib/cli/commands/list.ts
|
|
13380
|
-
import { basename as basename3 } from "node:path";
|
|
13381
13874
|
function workflowTypeToStatus(type) {
|
|
13382
13875
|
switch (type) {
|
|
13383
13876
|
case "refine":
|
|
@@ -13447,41 +13940,7 @@ function formatPrinciplesSection(header, entries) {
|
|
|
13447
13940
|
}
|
|
13448
13941
|
return lines;
|
|
13449
13942
|
}
|
|
13450
|
-
|
|
13451
|
-
const files = await fileSystem.readdir(principlesPath);
|
|
13452
|
-
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
13453
|
-
const relationships = [];
|
|
13454
|
-
const titleMap = new Map;
|
|
13455
|
-
for (const file of mdFiles) {
|
|
13456
|
-
const filePath = `${principlesPath}/${file}`;
|
|
13457
|
-
const content = await fileSystem.readFile(filePath);
|
|
13458
|
-
const artifact = parseArtifact(filePath, content);
|
|
13459
|
-
relationships.push(extractPrincipleRelationships(artifact));
|
|
13460
|
-
const title = extractTitle(content) || basename3(file, ".md");
|
|
13461
|
-
titleMap.set(filePath, title);
|
|
13462
|
-
}
|
|
13463
|
-
const relMap = new Map;
|
|
13464
|
-
for (const rel of relationships) {
|
|
13465
|
-
relMap.set(rel.filePath, rel);
|
|
13466
|
-
}
|
|
13467
|
-
const rootPrinciples = relationships.filter((rel) => rel.parentPrinciples.length === 0);
|
|
13468
|
-
function buildNode(filePath) {
|
|
13469
|
-
const rel = relMap.get(filePath);
|
|
13470
|
-
const children = [];
|
|
13471
|
-
if (rel) {
|
|
13472
|
-
for (const childPath of rel.subPrinciples) {
|
|
13473
|
-
children.push(buildNode(childPath));
|
|
13474
|
-
}
|
|
13475
|
-
}
|
|
13476
|
-
return {
|
|
13477
|
-
filePath,
|
|
13478
|
-
title: titleMap.get(filePath) || basename3(filePath, ".md"),
|
|
13479
|
-
children
|
|
13480
|
-
};
|
|
13481
|
-
}
|
|
13482
|
-
return rootPrinciples.map((rel) => buildNode(rel.filePath));
|
|
13483
|
-
}
|
|
13484
|
-
function renderHierarchy(nodes, output, prefix = "") {
|
|
13943
|
+
function renderRepositoryPrincipleHierarchy(nodes, output, prefix = "") {
|
|
13485
13944
|
for (let i = 0;i < nodes.length; i++) {
|
|
13486
13945
|
const node = nodes[i];
|
|
13487
13946
|
const isLastNode = i === nodes.length - 1;
|
|
@@ -13489,7 +13948,7 @@ function renderHierarchy(nodes, output, prefix = "") {
|
|
|
13489
13948
|
const childPrefix = isLastNode ? " " : "│ ";
|
|
13490
13949
|
output(`${prefix}${connector}${node.title}`);
|
|
13491
13950
|
if (node.children.length > 0) {
|
|
13492
|
-
|
|
13951
|
+
renderRepositoryPrincipleHierarchy(node.children, output, prefix + childPrefix);
|
|
13493
13952
|
}
|
|
13494
13953
|
}
|
|
13495
13954
|
}
|
|
@@ -13599,9 +14058,10 @@ async function processPrinciplesList(context) {
|
|
|
13599
14058
|
stdout("");
|
|
13600
14059
|
}
|
|
13601
14060
|
if (hasLocalPrinciples) {
|
|
13602
|
-
const
|
|
14061
|
+
const repository = buildReadOnlyArtifactsRepository(fileSystem, dustPath);
|
|
14062
|
+
const localHierarchy = await repository.getRepositoryPrincipleHierarchy();
|
|
13603
14063
|
stdout(`${colors.bold}Local${colors.reset}`);
|
|
13604
|
-
|
|
14064
|
+
renderRepositoryPrincipleHierarchy(localHierarchy, (line) => stdout(line));
|
|
13605
14065
|
stdout("");
|
|
13606
14066
|
const collectedItems = [];
|
|
13607
14067
|
for (const file of localMdFiles) {
|