@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/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.110",
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 timestamps = await Promise.all(files.map(async (file) => {
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 ts = result.exitCode === 0 ? Number.parseInt(result.output.trim(), 10) : Number.NaN;
400
- return {
401
- file,
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
- timestamps.sort((a, b) => a.timestamp - b.timestamp);
406
- return timestamps.map((t) => t.file);
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.110";
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 extractBlockedBy(content) {
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 blockers = [];
6532
+ const slugs = [];
6401
6533
  let match = linkPattern.exec(section);
6402
6534
  while (match !== null) {
6403
- blockers.push(match[1]);
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 blockers;
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
- let mdFiles = files.filter((f) => f.endsWith(".md"));
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 existingTasks = new Set(validTaskFiles.map((t) => t.file));
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 { file, content } of validTaskFiles) {
6454
- const blockers = extractBlockedBy(content);
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
- const relativePath = `.dust/tasks/${file}`;
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/index.ts
10736
- var ARTIFACT_TYPES = [
10737
- "facts",
10738
- "ideas",
10739
- "principles",
10740
- "tasks"
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/lint/validators/directory-validator.ts
10744
- var EXPECTED_DIRECTORIES = [...ARTIFACT_TYPES, "config"];
10745
- var EXPECTED_ROOT_FILES = ["repository.md"];
10746
- var EXPECTED_CONFIG_FILES = ["settings.json"];
10747
- var EXPECTED_CONFIG_DIRECTORIES = ["audits", "agents", "container", "hints"];
10748
- async function validateContentDirectoryFiles(dirPath, fileSystem) {
10749
- const violations = [];
10750
- let entries;
10751
- try {
10752
- entries = await fileSystem.readdir(dirPath);
10753
- } catch (error) {
10754
- if (isErrorCode(error, "ENOENT")) {
10755
- return [];
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
- for (const entry of entries) {
10760
- const entryPath = `${dirPath}/${entry}`;
10761
- if (entry.startsWith(".")) {
10762
- violations.push({
10763
- file: entryPath,
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
- if (fileSystem.isDirectory(entryPath)) {
10769
- violations.push({
10770
- file: entryPath,
10771
- message: `Subdirectory "${entry}" found in content directory (content directories should be flat)`
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 (!entry.endsWith(".md")) {
10776
- violations.push({
10777
- file: entryPath,
10778
- message: `Non-markdown file "${entry}" found in content directory`
10779
- });
10929
+ if (inCodeFence) {
10930
+ if (currentOption) {
10931
+ descriptionLines.push(line);
10932
+ }
10933
+ continue;
10780
10934
  }
10781
- }
10782
- return violations;
10783
- }
10784
- async function validateDirectoryStructure(dustPath, fileSystem) {
10785
- const violations = [];
10786
- let entries;
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
- throw error;
10794
- }
10795
- const allowedDirectories = new Set(EXPECTED_DIRECTORIES);
10796
- const allowedRootFiles = new Set(EXPECTED_ROOT_FILES);
10797
- const allowedRootList = [
10798
- ...[...allowedDirectories].toSorted().map((directory) => `${directory}/`),
10799
- ...EXPECTED_ROOT_FILES
10800
- ].join(", ");
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
- const isDirectory = fileSystem.isDirectory(entryPath);
10811
- if (!isDirectory) {
10812
- if (allowedRootFiles.has(entry)) {
10813
- continue;
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 (!allowedDirectories.has(entry)) {
10822
- violations.push({
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
- const configPath = `${dustPath}/config`;
10829
- if (!fileSystem.isDirectory(configPath)) {
10830
- return violations;
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
- let configEntries;
10833
- try {
10834
- configEntries = await fileSystem.readdir(configPath);
10835
- } catch (error) {
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 allowedConfigFiles = new Set(EXPECTED_CONFIG_FILES);
10842
- const allowedConfigDirectories = new Set(EXPECTED_CONFIG_DIRECTORIES);
10843
- const allowedConfigList = [
10844
- ...EXPECTED_CONFIG_DIRECTORIES.map((directory) => `${directory}/`),
10845
- ...EXPECTED_CONFIG_FILES
10846
- ].toSorted().join(", ");
10847
- for (const entry of configEntries) {
10848
- const entryPath = `${configPath}/${entry}`;
10849
- if (fileSystem.isDirectory(entryPath)) {
10850
- if (!allowedConfigDirectories.has(entry)) {
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: "batteries-included",
11727
- content: `# Batteries Included
12241
+ slug: "fast-feedback-loops",
12242
+ content: `# Fast Feedback Loops
11728
12243
 
11729
- Dust should provide everything that is required (within reason) for an agent to be productive in an arbitrary codebase.
12244
+ The primary feedback loop write code, run checks, see results should be as fast as possible.
11730
12245
 
11731
- 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.
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
- 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.
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
- ## Applicability
12250
+ ## Parent Principle
11736
12251
 
11737
- Internal
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
- - [Agent Autonomy](agent-autonomy.md)
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: "some-big-design-up-front",
11748
- content: `# Some Big Design Up Front
12283
+ slug: "boy-scout-rule",
12284
+ content: `# Boy Scout Rule
11749
12285
 
11750
- AI agents lower the cost of architectural exploration, making heavier upfront investment rational during the idea phase.
12286
+ Always leave the code better than you found it.
11751
12287
 
11752
- 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.
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
- 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.
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
- 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.
12292
+ ## Parent Principle
11757
12293
 
11758
- ## Convergence Criteria
12294
+ - [Maintainable Codebase](maintainable-codebase.md)
11759
12295
 
11760
- 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.
12296
+ ## Sub-Principles
11761
12297
 
11762
- When exploration feels "done":
12298
+ - (none)
12299
+ `
12300
+ },
12301
+ {
12302
+ slug: "atomic-commits",
12303
+ content: `# Atomic Commits
11763
12304
 
11764
- - Multiple approaches have been considered
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
- If a task requires significant design decisions during execution, it wasn't ready to be a task.
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
- ## Documenting Alternatives
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
- 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.
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
- - [Lightweight Planning](lightweight-planning.md)
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: "design-for-testability",
11786
- content: `# Design for Testability
12338
+ slug: "broken-windows",
12339
+ content: `# Broken Windows
11787
12340
 
11788
- Design code to be testable first; good structure follows naturally.
12341
+ Don't leave broken windows unrepaired.
11789
12342
 
11790
- 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.
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
- 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.
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 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.
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
- - [Decoupled Code](decoupled-code.md)
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: "readable-test-data",
11807
- content: `# Readable Test Data
12359
+ slug: "trunk-based-development",
12360
+ content: `# Trunk-Based Development
11808
12361
 
11809
- Test data setup should use natural structures that mirror what they represent.
12362
+ Dust is designed to support a non-branching workflow where developers commit directly to a single main branch.
11810
12363
 
11811
- ## Why it matters
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
- 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.
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
- Prefer literal structures that visually match the domain:
12416
+ Collapse multiple partial assertions into one comprehensive assertion:
11818
12417
 
11819
12418
  \`\`\`javascript
11820
- // Avoid: flat paths that obscure hierarchy
11821
- const fs = createFileSystemEmulator({
11822
- files: new Map([['/project/.dust/principles/my-goal.md', '# My Goal']]),
11823
- existingPaths: new Set(['/project/.dust/ideas']),
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
- // Prefer: nested object that mirrors file system structure
11827
- const fs = createFileSystemEmulator({
11828
- project: {
11829
- '.dust': {
11830
- principles: {
11831
- 'my-goal.md': '# My Goal'
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 nested form:
11840
- - Shows parent-child relationships through indentation
11841
- - Makes empty directories explicit with empty objects
11842
- - Requires no mental path concatenation to understand structure
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 setup data uses structures that visually resemble what they represent, reducing cognitive load for readers.
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
- - (none)
12499
+ - [Progressive Disclosure](progressive-disclosure.md)
11855
12500
  `
11856
12501
  },
11857
12502
  {
11858
- slug: "agent-specific-enhancement",
11859
- content: `# Agent-Specific Enhancement
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
- This principle complements Agent-Agnostic Design: the core functionality never requires a specific agent, but the experience improves when one is recognized.
12506
+ Dust exists to enable effective collaboration between humans and AI agents on complex projects.
11870
12507
 
11871
- ## Applicability
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
- Internal
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
- - [Agent Autonomy](agent-autonomy.md)
12514
+ - [Agentic Flow State](agentic-flow-state.md)
11878
12515
 
11879
12516
  ## Sub-Principles
11880
12517
 
11881
- - (none)
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: "context-optimised-code",
11886
- content: `# Context-Optimised Code
12525
+ slug: "functional-core-imperative-shell",
12526
+ content: `# Functional Core, Imperative Shell
11887
12527
 
11888
- Code should be structured so that agents can understand and modify it within their context window constraints.
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
- 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.
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
- 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.
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
- - [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
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: "self-diagnosing-tests",
11905
- content: `# Self-Diagnosing Tests
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
- 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.
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
- ## Anti-patterns
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
- **Boolean flattening**collapsing a rich value into true/false before asserting:
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
- // Good: shows the actual event types on failure
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
- **Length-only assertions** — checking count without showing contents:
11923
- \`\`\`javascript
11924
- // Bad: "expected 2, received 0" — what requests were captured?
11925
- expect(requests.length).toBe(2)
12559
+ ## Migration Guidance
11926
12560
 
11927
- // Good: shows the actual requests on failure
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
- **Silent guards** — using \`if\` where an assertion belongs:
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
- // Good: fails explicitly if settings is missing
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
- ## The test
12567
+ ## Sub-Principles
11945
12568
 
11946
- 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.
12569
+ - (none)
12570
+ `
12571
+ },
12572
+ {
12573
+ slug: "runtime-agnostic-tests",
12574
+ content: `# Runtime Agnostic Tests
11947
12575
 
11948
- ## How to evaluate
12576
+ Dust's test suite should work across JavaScript runtimes.
11949
12577
 
11950
- 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.
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
- - [Make Changes with Confidence](make-changes-with-confidence.md)
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: "ideal-agent-developer-experience",
11963
- content: `# Ideal Agent Developer Experience
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
- 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.
12593
+ The user interface should be as "guessable" as possible.
11968
12594
 
11969
- 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.
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
- 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.
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
- - [Human-AI Collaboration](human-ai-collaboration.md)
12601
+ - [Easy Adoption](easy-adoption.md)
11976
12602
 
11977
12603
  ## Sub-Principles
11978
12604
 
11979
- - [Comprehensive Test Coverage](comprehensive-test-coverage.md)
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: "broken-windows",
11991
- content: `# Broken Windows
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
- 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.
12612
+ Complete unit test coverage ensures low-level tests give users direct feedback as they change the code.
11998
12613
 
11999
- 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.
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
- - [Maintainable Codebase](maintainable-codebase.md)
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: "progressive-disclosure",
12012
- content: `# Progressive Disclosure
12626
+ slug: "cross-platform-compatibility",
12627
+ content: `# Cross-Platform Compatibility
12013
12628
 
12014
- Dust should reveal details progressively as a way of achieving context window efficiency.
12629
+ Dust should work consistently across operating systems: Linux, macOS, and Windows.
12015
12630
 
12016
- 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.
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
- This layered approach keeps initial reads lightweight while preserving access to complete information when needed.
12637
+ Cross-platform support broadens adoption and ensures teams with mixed environments can collaborate effectively.
12019
12638
 
12020
12639
  ## Parent Principle
12021
12640
 
12022
- - [Context Window Efficiency](context-window-efficiency.md)
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: "lightweight-planning",
12031
- content: `# Lightweight Planning
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
- 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.
12652
+ Dust should work independently of any specific version control system.
12036
12653
 
12037
- The system avoids the staleness problem by deferring detail until the last responsible moment and deleting completed work rather than archiving it.
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
- - [Human-AI Collaboration](human-ai-collaboration.md)
12658
+ - [Easy Adoption](easy-adoption.md)
12042
12659
 
12043
12660
  ## Sub-Principles
12044
12661
 
12045
- - [Task-First Workflow](task-first-workflow.md)
12046
- - [Some Big Design Up Front](some-big-design-up-front.md)
12662
+ - (none)
12047
12663
  `
12048
12664
  },
12049
12665
  {
12050
- slug: "comprehensive-test-coverage",
12051
- content: `# Comprehensive Test Coverage
12666
+ slug: "self-contained-repository",
12667
+ content: `# Self-Contained Repository
12052
12668
 
12053
- A project's test suite is its primary safety net, and agents depend on it even more than humans do.
12669
+ Where possible, developers and agents should have everything they need to be productive, within the repository.
12054
12670
 
12055
- 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.
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
- Dust should help projects measure and improve their test coverage, flag untested areas, and encourage a culture where new code comes with new tests.
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: "intuitive-directory-structure",
12070
- content: `# Intuitive Directory Structure
12687
+ slug: "minimal-dependencies",
12688
+ content: `# Minimal Dependencies
12071
12689
 
12072
- Code should be organized around related concerns in clearly named directories.
12690
+ Dust should avoid coupling to specific tools so we can switch to better alternatives as they emerge.
12073
12691
 
12074
- 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.
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
- - [Co-located Tests](co-located-tests.md)
12700
+ - [Runtime Agnostic Tests](runtime-agnostic-tests.md)
12083
12701
  `
12084
12702
  },
12085
12703
  {
12086
- slug: "small-units",
12087
- content: `# Small Units
12704
+ slug: "agent-specific-enhancement",
12705
+ content: `# Agent-Specific Enhancement
12088
12706
 
12089
- Ideas, principles, facts, and tasks should each be as discrete and fine-grained as possible.
12707
+ Dust should detect and enhance the experience for specific agents while remaining agnostic at its core.
12090
12708
 
12091
- 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.
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
- 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.
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
- Note: This principle directly supports [Lightweight Planning](lightweight-planning.md), which explicitly mentions that "Tasks are small and completable in single commits."
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: "fast-feedback",
12108
- content: `# Fast Feedback
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
- ## Parent Principle
12734
+ When a big test fails, it should be self-evident how to diagnose and fix the failure.
12115
12735
 
12116
- - [Make Changes with Confidence](make-changes-with-confidence.md)
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
- ## Sub-Principles
12738
+ ## Anti-patterns
12119
12739
 
12120
- - (none)
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
- Avoid global mocks. Dependency injection is almost always preferable to testing code that depends directly on globals.
12745
+ // Good: shows the actual event types on failure
12746
+ expect(events.map(e => e.type)).toContain('check-passed')
12747
+ \`\`\`
12128
12748
 
12129
- When code depends on global state or singletons, testing requires mocking those globalswhich 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.
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
- 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.
12754
+ // Good: shows the actual requests on failure
12755
+ expect(requests).toHaveLength(2) // vitest shows the array
12756
+ \`\`\`
12132
12757
 
12133
- ## Parent Principle
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
- - [Decoupled Code](decoupled-code.md)
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
- ## Sub-Principles
12771
+ ## The test
12138
12772
 
12139
- - (none)
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
- 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.
12775
+ ## How to evaluate
12147
12776
 
12148
- 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.
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: "make-changes-with-confidence",
12180
- content: `# Make Changes with Confidence
12808
+ slug: "agentic-flow-state",
12809
+ content: `# Agentic Flow State
12181
12810
 
12182
- Developers should be able to modify code without fear of breaking existing behavior.
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
- 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.
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
- - [Maintainable Codebase](maintainable-codebase.md)
12827
+ - (none)
12189
12828
 
12190
12829
  ## Sub-Principles
12191
12830
 
12192
- - [Comprehensive Assertions](comprehensive-assertions.md)
12193
- - [Decoupled Code](decoupled-code.md)
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: "test-isolation",
12207
- content: `# Test Isolation
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
- This means:
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
- Test isolation enables parallel execution, makes failures easier to diagnose, and prevents cascading false failures when one test breaks.
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
- - [Environment-Independent Tests](environment-independent-tests.md)
12849
+ - (none)
12226
12850
  `
12227
12851
  },
12228
12852
  {
12229
- slug: "repository-hygiene",
12230
- content: `# Repository Hygiene
12853
+ slug: "task-first-workflow",
12854
+ content: `# Task-First Workflow
12231
12855
 
12232
- Dust repositories should maintain a clean, organized state with minimal noise.
12856
+ Work should be captured as a task before implementation begins, creating traceability between intent and outcome.
12233
12857
 
12234
- 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.
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
- - [Maintainable Codebase](maintainable-codebase.md)
12862
+ - [Lightweight Planning](lightweight-planning.md)
12239
12863
 
12240
12864
  ## Sub-Principles
12241
12865
 
12242
- - [Atomic Commits](atomic-commits.md)
12243
- - [Trunk-Based Development](trunk-based-development.md)
12866
+ - (none)
12244
12867
  `
12245
12868
  },
12246
12869
  {
12247
- slug: "agentic-flow-state",
12248
- content: `# Agentic Flow State
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
- 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.
12873
+ The agent is the developer. The human is the CEO. Dust is the PM.
12253
12874
 
12254
- Dust's design targets these conditions directly:
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
- - **Clear goals**: Task files and lightweight planning give you a concrete target. You know exactly what you're building next.
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
- Everything dust does serves flow. When agents stay in flow, they produce better work, sustain their momentum, and complete tasks autonomously.
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
- - (none)
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: "naming-matters",
12316
- content: `# Naming Matters
12921
+ slug: "agent-autonomy",
12922
+ content: `# Agent Autonomy
12317
12923
 
12318
- Good naming reduces waste by eliminating confusion and making code self-documenting.
12924
+ Dust exists to enable AI agents to produce work autonomously.
12319
12925
 
12320
- 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.
12926
+ With sufficient planning and small enough units, this works much better in practice.
12321
12927
 
12322
12928
  ## Parent Principle
12323
12929
 
12324
- - [Maintainable Codebase](maintainable-codebase.md)
12930
+ - [Human-AI Collaboration](human-ai-collaboration.md)
12325
12931
 
12326
12932
  ## Sub-Principles
12327
12933
 
12328
- - [Consistent Naming](consistent-naming.md)
12329
- - [Clarity Over Brevity](clarity-over-brevity.md)
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: "functional-core-imperative-shell",
12357
- content: `# Functional Core, Imperative Shell
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
- Structured logging and tracing help agents understand system behaviour without resorting to ad-hoc testing cycles.
12970
+ Agents need effective tools for diagnosing and fixing issues without manual intervention.
12379
12971
 
12380
- 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.
12972
+ Traditional debugging relies on breakpoints, stepping through code, and interactive inspectiontechniques 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 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.
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: "keep-unit-tests-pure",
12399
- content: `# Keep Unit Tests Pure
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
- Test files should live next to the code they test.
12993
+ Names should follow established conventions within each category to reduce cognitive load.
12431
12994
 
12432
- 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.
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
- - [Intuitive Directory Structure](intuitive-directory-structure.md)
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: "human-ai-collaboration",
12445
- content: `# Human-AI Collaboration
13007
+ slug: "lightweight-planning",
13008
+ content: `# Lightweight Planning
12446
13009
 
12447
- Dust exists to enable effective collaboration between humans and AI agents on complex projects.
13010
+ Dust aims to be a minimal, low-overhead planning system that stays relevant over time.
12448
13011
 
12449
- 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.
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
- 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.
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
- - [Agentic Flow State](agentic-flow-state.md)
13018
+ - [Human-AI Collaboration](human-ai-collaboration.md)
12456
13019
 
12457
13020
  ## Sub-Principles
12458
13021
 
12459
- - [Agent Autonomy](agent-autonomy.md)
12460
- - [Easy Adoption](easy-adoption.md)
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: "vcs-independence",
12467
- content: `# VCS Independence
13027
+ slug: "easy-adoption",
13028
+ content: `# Easy Adoption
12468
13029
 
12469
- Dust should work independently of any specific version control system.
13030
+ Dust should be trivially easy to adopt in any repository.
12470
13031
 
12471
- 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.
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
- - [Easy Adoption](easy-adoption.md)
13038
+ - [Human-AI Collaboration](human-ai-collaboration.md)
12476
13039
 
12477
13040
  ## Sub-Principles
12478
13041
 
12479
- - (none)
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: "environment-independent-tests",
12484
- content: `# Environment-Independent Tests
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
- Concretely, tests should never depend on:
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 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.
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
- - [Test Isolation](test-isolation.md)
13057
+ - [Maintainable Codebase](maintainable-codebase.md)
12499
13058
 
12500
13059
  ## Sub-Principles
12501
13060
 
12502
- - (none)
13061
+ - [Co-located Tests](co-located-tests.md)
12503
13062
  `
12504
13063
  },
12505
13064
  {
12506
- slug: "debugging-tooling",
12507
- content: `# Debugging Tooling
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
- 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.
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
- ## Applicability
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
- Internal
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
- - [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
13080
+ - [Make Changes with Confidence](make-changes-with-confidence.md)
12522
13081
 
12523
13082
  ## Sub-Principles
12524
13083
 
12525
- - (none)
13084
+ (none)
12526
13085
  `
12527
13086
  },
12528
13087
  {
12529
- slug: "atomic-commits",
12530
- content: `# Atomic Commits
13088
+ slug: "progressive-disclosure",
13089
+ content: `# Progressive Disclosure
12531
13090
 
12532
- Each commit should tell a complete story, bundling implementation changes with their corresponding documentation updates.
13091
+ Dust should reveal details progressively as a way of achieving context window efficiency.
12533
13092
 
12534
- 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.
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
- 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.
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
- - [Repository Hygiene](repository-hygiene.md)
13099
+ - [Context Window Efficiency](context-window-efficiency.md)
12541
13100
 
12542
13101
  ## Sub-Principles
12543
13102
 
12544
- - [Traceable Decisions](traceable-decisions.md)
13103
+ - (none)
12545
13104
  `
12546
13105
  },
12547
13106
  {
12548
- slug: "trunk-based-development",
12549
- content: `# Trunk-Based Development
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
- 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.
13110
+ Code should be structured so that agents can understand and modify it within their context window constraints.
12556
13111
 
12557
- 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.
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
- See: https://trunkbaseddevelopment.com/
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
- - [Repository Hygiene](repository-hygiene.md)
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: "comprehensive-assertions",
12572
- content: `# Comprehensive Assertions
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
- Small assertions are like yes/no questions to a witness. A whole-object assertion is like asking "tell me what you saw."
13129
+ AI agents lower the cost of architectural exploration, making heavier upfront investment rational during the idea phase.
12579
13130
 
12580
- ## In practice
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
- Collapse multiple partial assertions into one comprehensive assertion:
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
- \`\`\`javascript
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
- // Whole — a failure diff tells the full story
12591
- expect(result).toEqual({
12592
- name: "Alice",
12593
- age: 30,
12594
- role: "admin",
12595
- });
12596
- \`\`\`
13137
+ ## Convergence Criteria
12597
13138
 
12598
- 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.
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
- The same applies to arrays:
13141
+ When exploration feels "done":
12601
13142
 
12602
- \`\`\`javascript
12603
- // Avoid: partial assertions that hide the actual state
12604
- expect(array).toContain('apples')
12605
- expect(array).toContain('oranges')
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
- // Prefer: one assertion that reveals the full picture on failure
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
- ## How to evaluate
13150
+ ## Documenting Alternatives
12612
13151
 
12613
- 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.
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
- - [Make Changes with Confidence](make-changes-with-confidence.md)
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: "cross-platform-compatibility",
12626
- content: `# Cross-Platform Compatibility
12627
-
12628
- Dust should work consistently across operating systems: Linux, macOS, and Windows.
13164
+ slug: "traceable-decisions",
13165
+ content: `# Traceable Decisions
12629
13166
 
12630
- This means:
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
- Cross-platform support broadens adoption and ensures teams with mixed environments can collaborate effectively.
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
- - [Easy Adoption](easy-adoption.md)
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: "exploratory-tooling",
12649
- content: `# Exploratory Tooling
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
- ## Applicability
13184
+ Dust should provide fast feedback loops for developers.
12658
13185
 
12659
- Internal
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
- - [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
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: "reasonably-dry",
12672
- content: `# Reasonably DRY
13198
+ slug: "decoupled-code",
13199
+ content: `# Decoupled Code
12673
13200
 
12674
- Don't repeat yourself is a good principle, but don't overdo it.
13201
+ Code should be organized into independent units with explicit dependencies.
12675
13202
 
12676
- 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.
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
- - [Maintainable Codebase](maintainable-codebase.md)
13207
+ - [Make Changes with Confidence](make-changes-with-confidence.md)
12681
13208
 
12682
13209
  ## Sub-Principles
12683
13210
 
12684
- - (none)
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: "runtime-agnostic-tests",
12689
- content: `# Runtime Agnostic Tests
13218
+ slug: "make-changes-with-confidence",
13219
+ content: `# Make Changes with Confidence
12690
13220
 
12691
- Dust's test suite should work across JavaScript runtimes.
13221
+ Developers should be able to modify code without fear of breaking existing behavior.
12692
13222
 
12693
- 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.
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
- - [Minimal Dependencies](minimal-dependencies.md)
13227
+ - [Maintainable Codebase](maintainable-codebase.md)
12698
13228
 
12699
13229
  ## Sub-Principles
12700
13230
 
12701
- - (none)
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: "task-first-workflow",
12706
- content: `# Task-First Workflow
13245
+ slug: "clarity-over-brevity",
13246
+ content: `# Clarity Over Brevity
12707
13247
 
12708
- Work should be captured as a task before implementation begins, creating traceability between intent and outcome.
13248
+ Names should be descriptive and self-documenting, even if longer.
12709
13249
 
12710
- 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.
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
- - [Lightweight Planning](lightweight-planning.md)
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-autonomy",
12723
- content: `# Agent Autonomy
13262
+ slug: "agent-agnostic-design",
13263
+ content: `# Agent-Agnostic Design
12724
13264
 
12725
- Dust exists to enable AI agents to produce work autonomously.
13265
+ Dust should work with multiple agents without favoring one.
12726
13266
 
12727
- With sufficient planning and small enough units, this works much better in practice.
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
- - [Human-AI Collaboration](human-ai-collaboration.md)
13279
+ - [Agent Autonomy](agent-autonomy.md)
12732
13280
 
12733
13281
  ## Sub-Principles
12734
13282
 
12735
- - [Actionable Errors](actionable-errors.md)
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: "clarity-over-brevity",
12746
- content: `# Clarity Over Brevity
13287
+ slug: "readable-test-data",
13288
+ content: `# Readable Test Data
12747
13289
 
12748
- Names should be descriptive and self-documenting, even if longer.
13290
+ Test data setup should use natural structures that mirror what they represent.
12749
13291
 
12750
- 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.
13292
+ ## Why it matters
12751
13293
 
12752
- ## Parent Principle
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
- - [Naming Matters](naming-matters.md)
13296
+ ## In practice
12755
13297
 
12756
- ## Sub-Principles
13298
+ Prefer literal structures that visually match the domain:
12757
13299
 
12758
- - (none)
12759
- `
12760
- },
12761
- {
12762
- slug: "fast-feedback-loops",
12763
- content: `# Fast Feedback Loops
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
- The primary feedback loop write code, run checks, see results — should be as fast as possible.
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
- 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.
13325
+ ## How to evaluate
12768
13326
 
12769
- 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.
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
- - [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
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: "make-the-change-easy",
12782
- content: `# Make the Change Easy
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
- 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.
13342
+ Don't repeat yourself is a good principle, but don't overdo it.
12787
13343
 
12788
- 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.
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: "self-contained-repository",
12801
- content: `# Self-Contained Repository
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
- 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.
13359
+ Error messages should tell you what to do next, not just what went wrong.
12806
13360
 
12807
- ## Applicability
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
- Internal
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
- - [Ideal Agent Developer Experience](ideal-agent-developer-experience.md)
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: "traceable-decisions",
12822
- content: `# Traceable Decisions
13378
+ slug: "make-the-change-easy",
13379
+ content: `# Make the Change Easy
12823
13380
 
12824
- The commit history should explain why changes were made, not just what changed.
13381
+ For each desired change, make the change easy, then make the easy change.
12825
13382
 
12826
- 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.
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
- - [Atomic Commits](atomic-commits.md)
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: "unit-test-coverage",
12839
- content: `# Unit Test Coverage
13397
+ slug: "dependency-injection",
13398
+ content: `# Dependency Injection
12840
13399
 
12841
- Complete unit test coverage ensures low-level tests give users direct feedback as they change the code.
13400
+ Avoid global mocks. Dependency injection is almost always preferable to testing code that depends directly on globals.
12842
13401
 
12843
- 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.
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
- - [Make Changes with Confidence](make-changes-with-confidence.md)
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: "decoupled-code",
12856
- content: `# Decoupled Code
13416
+ slug: "repository-hygiene",
13417
+ content: `# Repository Hygiene
12857
13418
 
12858
- Code should be organized into independent units with explicit dependencies.
13419
+ Dust repositories should maintain a clean, organized state with minimal noise.
12859
13420
 
12860
- 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.
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
- - [Make Changes with Confidence](make-changes-with-confidence.md)
13425
+ - [Maintainable Codebase](maintainable-codebase.md)
12865
13426
 
12866
13427
  ## Sub-Principles
12867
13428
 
12868
- - [Dependency Injection](dependency-injection.md)
12869
- - [Stubs Over Mocks](stubs-over-mocks.md)
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: "lint-everything",
12876
- content: `# Lint Everything
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
- - [Make Changes with Confidence](make-changes-with-confidence.md)
13437
+ Dust should provide everything that is required (within reason) for an agent to be productive in an arbitrary codebase.
12891
13438
 
12892
- ## Sub-Principles
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
- (none)
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
- The dust codebase should be easy to understand, modify, and extend.
13443
+ ## Applicability
12902
13444
 
12903
- 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.
13445
+ Internal
12904
13446
 
12905
13447
  ## Parent Principle
12906
13448
 
12907
- - [Agentic Flow State](agentic-flow-state.md)
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: "agent-agnostic-design",
12924
- content: `# Agent-Agnostic Design
12925
-
12926
- Dust should work with multiple agents without favoring one.
13455
+ slug: "development-traceability",
13456
+ content: `# Development Traceability
12927
13457
 
12928
- 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.
13458
+ Structured logging and tracing help agents understand system behaviour without resorting to ad-hoc testing cycles.
12929
13459
 
12930
- 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.
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
- 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.
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 Autonomy](agent-autonomy.md)
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: "easy-adoption",
12949
- content: `# Easy Adoption
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
- ## Sub-Principles
13481
+ Agents need tools to efficiently explore and understand unfamiliar codebases.
12962
13482
 
12963
- - [Cross-Platform Compatibility](cross-platform-compatibility.md)
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
- Error messages should tell you what to do next, not just what went wrong.
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
- When something fails, the message should provide:
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
- 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.
13489
+ Internal
12980
13490
 
12981
13491
  ## Parent Principle
12982
13492
 
12983
- - [Agent Autonomy](agent-autonomy.md)
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: "consistent-naming",
12992
- content: `# Consistent Naming
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
- ## Sub-Principles
13504
+ Ideas, principles, facts, and tasks should each be as discrete and fine-grained as possible.
13003
13505
 
13004
- - (none)
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
- Dust should avoid coupling to specific tools so we can switch to better alternatives as they emerge.
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
- 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.
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
- - [Maintainable Codebase](maintainable-codebase.md)
13514
+ - [Agent Autonomy](agent-autonomy.md)
13018
13515
 
13019
13516
  ## Sub-Principles
13020
13517
 
13021
- - [Runtime Agnostic Tests](runtime-agnostic-tests.md)
13518
+ - (none)
13022
13519
  `
13023
13520
  },
13024
13521
  {
13025
- slug: "context-window-efficiency",
13026
- content: `# Context Window Efficiency
13027
-
13028
- Dust should be designed with short attention spans in mind.
13522
+ slug: "naming-matters",
13523
+ content: `# Naming Matters
13029
13524
 
13030
- 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.
13525
+ Good naming reduces waste by eliminating confusion and making code self-documenting.
13031
13526
 
13032
- This means favoring brevity over completeness, using consistent structures that are fast to parse, and avoiding redundant information across files.
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
- - [Agent Autonomy](agent-autonomy.md)
13531
+ - [Maintainable Codebase](maintainable-codebase.md)
13037
13532
 
13038
13533
  ## Sub-Principles
13039
13534
 
13040
- - [Progressive Disclosure](progressive-disclosure.md)
13535
+ - [Consistent Naming](consistent-naming.md)
13536
+ - [Clarity Over Brevity](clarity-over-brevity.md)
13041
13537
  `
13042
13538
  },
13043
13539
  {
13044
- slug: "boy-scout-rule",
13045
- content: `# Boy Scout Rule
13540
+ slug: "comprehensive-test-coverage",
13541
+ content: `# Comprehensive Test Coverage
13046
13542
 
13047
- Always leave the code better than you found it.
13543
+ A project's test suite is its primary safety net, and agents depend on it even more than humans do.
13048
13544
 
13049
- When working in any area of the codebase, take the opportunity to make small improvements clearer names, removed dead code, better structureeven 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.
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 capabilityareas 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
- 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.
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
- - [Maintainable Codebase](maintainable-codebase.md)
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: "unsurprising-ux",
13064
- content: `# Unsurprising UX
13065
-
13066
- The user interface should be as "guessable" as possible.
13559
+ slug: "stop-the-line",
13560
+ content: `# Stop the Line
13067
13561
 
13068
- 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.
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
- Surprising behavior erodes trust and slows people down. Unsurprising behavior lets users stay in flow.
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
- - [Easy Adoption](easy-adoption.md)
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 sortNodes(nodes) {
13578
+ function sortNodes2(nodes) {
13085
13579
  nodes.sort((a, b) => a.title.localeCompare(b.title));
13086
13580
  for (const node of nodes) {
13087
- sortNodes(node.children);
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
- sortNodes(roots);
13625
+ sortNodes2(roots);
13132
13626
  return roots;
13133
13627
  }
13134
13628
 
13135
13629
  // lib/core-principles.ts
13136
- function extractLinksFromSection(content, sectionHeading) {
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 extractSingleLinkFromSection(content, sectionHeading) {
13162
- const links = extractLinksFromSection(content, sectionHeading);
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 = extractSingleLinkFromSection(content, "Parent Principle");
13171
- const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
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
- async function buildPrincipleHierarchy(principlesPath, fileSystem) {
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
- renderHierarchy(node.children, output, prefix + childPrefix);
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 localHierarchy = await buildPrincipleHierarchy(localDirPath, fileSystem);
14061
+ const repository = buildReadOnlyArtifactsRepository(fileSystem, dustPath);
14062
+ const localHierarchy = await repository.getRepositoryPrincipleHierarchy();
13603
14063
  stdout(`${colors.bold}Local${colors.reset}`);
13604
- renderHierarchy(localHierarchy, (line) => stdout(line));
14064
+ renderRepositoryPrincipleHierarchy(localHierarchy, (line) => stdout(line));
13605
14065
  stdout("");
13606
14066
  const collectedItems = [];
13607
14067
  for (const file of localMdFiles) {