@joshski/dust 0.1.109 → 0.1.111

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.109",
10
+ version: "0.1.111",
11
11
  description: "Flow state for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
@@ -721,7 +721,7 @@ async function loadSettings(cwd, fileSystem, runtime) {
721
721
  }
722
722
 
723
723
  // lib/version.ts
724
- var DUST_VERSION = "0.1.109";
724
+ var DUST_VERSION = "0.1.111";
725
725
 
726
726
  // lib/cli/middleware.ts
727
727
  function applyMiddleware(middlewares, execute) {
@@ -2895,6 +2895,168 @@ function slowTests() {
2895
2895
  - No changes to files outside \`.dust/\`
2896
2896
  `;
2897
2897
  }
2898
+ function overAbstraction() {
2899
+ return dedent`
2900
+ # Over-Abstraction
2901
+
2902
+ Identify violations of the "reasonably-dry" principle where code has been over-engineered with excessive abstraction.
2903
+
2904
+ ${ideasHint}
2905
+
2906
+ ## Scope
2907
+
2908
+ Detect these over-abstraction patterns:
2909
+
2910
+ 1. **Single-use abstractions** - Interfaces, base classes, or utility functions used in only one place
2911
+ 2. **Deep inheritance hierarchies** - Classes extending more than 2 levels deep
2912
+ 3. **Premature generalization** - Parameters always used with the same value, unused options/flags
2913
+ 4. **Excessive indirection** - Multiple layers of wrappers adding no value
2914
+
2915
+ ## Analysis Steps
2916
+
2917
+ ### 1. Find Single-Use Abstractions
2918
+
2919
+ Search for abstractions that are only used once:
2920
+
2921
+ 1. **Interfaces with one implementation**
2922
+ - Search for \`interface\` declarations
2923
+ - Check if each interface has only one implementing class
2924
+ - Flag interfaces that exist solely for testing (can be replaced with the concrete type)
2925
+
2926
+ 2. **Base classes with one subclass**
2927
+ - Search for \`abstract class\` or classes used as base classes
2928
+ - Count implementations extending each base class
2929
+ - Flag base classes with only one subclass
2930
+
2931
+ 3. **Utility functions called once**
2932
+ - Search for exported utility functions
2933
+ - Check call sites - if only called from one location, it's over-abstraction
2934
+ - Consider inlining single-use utilities
2935
+
2936
+ 4. **Generic types with one concrete usage**
2937
+ - Find generic type parameters: \`<T>\`, \`<TData>\`, etc.
2938
+ - Check if T is always the same type at all call sites
2939
+ - Flag generics that could be concrete types
2940
+
2941
+ ### 2. Detect Deep Inheritance Hierarchies
2942
+
2943
+ Find inheritance chains longer than 2 levels:
2944
+
2945
+ 1. Search for \`extends\` keywords in class declarations
2946
+ 2. Build inheritance tree for each class
2947
+ 3. Flag chains deeper than 2 (A extends B extends C extends D...)
2948
+ 4. Respect framework conventions (don't flag React.Component, etc.)
2949
+
2950
+ ### 3. Identify Premature Generalization
2951
+
2952
+ Look for flexibility that's never used:
2953
+
2954
+ 1. **Always-same parameter values**
2955
+ - Find function parameters
2956
+ - Check all call sites - if always the same value, it's not needed
2957
+ - Flag parameters that could be constants or removed
2958
+
2959
+ 2. **Unused configuration options**
2960
+ - Search for configuration objects/interfaces
2961
+ - Check which options are actually used
2962
+ - Flag options that are never set or always default
2963
+
2964
+ 3. **Unused function parameters**
2965
+ - Find parameters that aren't referenced in function bodies
2966
+ - Flag as candidates for removal
2967
+
2968
+ ### 4. Find Excessive Indirection
2969
+
2970
+ Detect wrapper chains that add no value:
2971
+
2972
+ 1. **Delegation chains**
2973
+ - Search for functions that only call another function
2974
+ - Flag wrappers that don't add logic, just forward calls
2975
+ - Example: \`function foo(x) { return bar(x) }\`
2976
+
2977
+ 2. **Proxy patterns without behavior**
2978
+ - Find classes that wrap another class
2979
+ - Check if wrapper adds any logic beyond forwarding
2980
+ - Flag pure proxies
2981
+
2982
+ 3. **Middleware without transformation**
2983
+ - Look for middleware/interceptor patterns
2984
+ - Check if they modify data or just pass through
2985
+ - Flag pass-through middleware
2986
+
2987
+ ## Output Format
2988
+
2989
+ For each over-abstraction found, create an idea file in \`.dust/ideas/\` with:
2990
+
2991
+ \`\`\`markdown
2992
+ # Over-Abstraction: [Type] in [Location]
2993
+
2994
+ ## Type
2995
+
2996
+ [Single-use | Deep hierarchy | Premature generalization | Excessive indirection]
2997
+
2998
+ ## Location
2999
+
3000
+ \`\`\`
3001
+ [file path]:[line number]
3002
+ \`\`\`
3003
+
3004
+ ## Description
3005
+
3006
+ [What the abstraction is]
3007
+
3008
+ ## Problem
3009
+
3010
+ [Why this is over-abstraction - complexity without benefit]
3011
+
3012
+ ## Usage Analysis
3013
+
3014
+ - **Times used**: [count]
3015
+ - **Variation in usage**: [how different are the use cases]
3016
+ - **Complexity cost**: [lines of code, indirection levels, etc.]
3017
+
3018
+ ## Suggested Simplification
3019
+
3020
+ [How to remove or reduce this abstraction]
3021
+
3022
+ ## Impact
3023
+
3024
+ [Lines of code saved, reduced complexity, improved clarity]
3025
+ \`\`\`
3026
+
3027
+ ## Special Considerations
3028
+
3029
+ 1. **Framework conventions** - Don't flag patterns mandated by frameworks:
3030
+ - React: Component base classes, hooks patterns
3031
+ - Express: Middleware signatures
3032
+ - Testing: Test base classes, fixture patterns
3033
+
3034
+ 2. **Library boundaries** - Public API abstractions may be justified even if internal usage is simple
3035
+
3036
+ 3. **Test code** - Apply the same standards to test code as production code
3037
+
3038
+ 4. **Context depth thresholds**:
3039
+ - Deep hierarchies (>2 levels) make understanding difficult
3040
+ - Wrapper chains (>2 levels) obscure actual behavior
3041
+ - Generic parameters should have multiple concrete usages
3042
+
3043
+ ## Blocked By
3044
+
3045
+ (none)
3046
+
3047
+ ## Definition of Done
3048
+
3049
+ - Searched for single-use interfaces, base classes, and utility functions
3050
+ - Identified deep inheritance hierarchies (>2 levels)
3051
+ - Found parameters always used with the same value
3052
+ - Detected unused configuration options
3053
+ - Located excessive wrapper chains and delegation
3054
+ - Respected framework conventions (didn't flag framework-mandated patterns)
3055
+ - Created idea files for each over-abstraction found
3056
+ - Each idea includes usage analysis and simplification suggestions
3057
+ - No changes to files outside \`.dust/\`
3058
+ `;
3059
+ }
2898
3060
  function primitiveObsession() {
2899
3061
  return dedent`
2900
3062
  # Primitive Obsession
@@ -4270,6 +4432,7 @@ var stockAuditFunctions = {
4270
4432
  "idiomatic-style": idiomaticStyle,
4271
4433
  "incidental-test-details": incidentalTestDetails,
4272
4434
  "logging-and-traceability": loggingAndTraceability,
4435
+ "over-abstraction": overAbstraction,
4273
4436
  "primitive-obsession": primitiveObsession,
4274
4437
  "repository-context": repositoryContext,
4275
4438
  "security-review": securityReview,
@@ -5754,6 +5917,8 @@ function buildImplementationInstructions(bin, hooksInstalled, taskTitle, taskPat
5754
5917
  steps.push(`${step}. Run \`${bin} check\` to verify the project is in a good state`);
5755
5918
  step++;
5756
5919
  }
5920
+ steps.push(`${step}. If the task file contains Principles and Guidance sections, read and follow them before implementing changes`);
5921
+ step++;
5757
5922
  steps.push(`${step}. Implement the task`);
5758
5923
  step++;
5759
5924
  if (!hooksInstalled) {
@@ -6081,6 +6246,55 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
6081
6246
  }
6082
6247
  return { captureIdeaTasks, workflowTasksByIdeaSlug };
6083
6248
  }
6249
+ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
6250
+ const ideaPath = `${dustPath}/ideas/${ideaSlug}.md`;
6251
+ if (!fileSystem.exists(ideaPath)) {
6252
+ throw new Error(`Idea not found: "${ideaSlug}" (expected file at ${ideaPath})`);
6253
+ }
6254
+ const tasksPath = `${dustPath}/tasks`;
6255
+ if (!fileSystem.exists(tasksPath)) {
6256
+ return null;
6257
+ }
6258
+ const files = await fileSystem.readdir(tasksPath);
6259
+ for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
6260
+ const content = await fileSystem.readFile(`${tasksPath}/${file}`);
6261
+ const taskSlug = file.replace(/\.md$/, "");
6262
+ const match = findWorkflowMatch(content, ideaSlug, taskSlug);
6263
+ if (match) {
6264
+ return match;
6265
+ }
6266
+ }
6267
+ return null;
6268
+ }
6269
+ function findWorkflowMatch(content, ideaSlug, taskSlug) {
6270
+ const taskType = parseTaskType(content);
6271
+ if (taskType) {
6272
+ const heading = WORKFLOW_SECTION_HEADINGS.find((h) => h.type === taskType)?.heading;
6273
+ if (heading) {
6274
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
6275
+ if (linkedSlug === ideaSlug) {
6276
+ return {
6277
+ type: taskType,
6278
+ ideaSlug,
6279
+ taskSlug,
6280
+ resolvedQuestions: parseResolvedQuestions(content)
6281
+ };
6282
+ }
6283
+ }
6284
+ }
6285
+ for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
6286
+ const linkedSlug = extractIdeaSlugFromSection(content, heading);
6287
+ if (linkedSlug === ideaSlug) {
6288
+ return {
6289
+ type,
6290
+ ideaSlug,
6291
+ taskSlug,
6292
+ resolvedQuestions: parseResolvedQuestions(content)
6293
+ };
6294
+ }
6295
+ }
6296
+ return null;
6297
+ }
6084
6298
  function parseResolvedQuestions(content) {
6085
6299
  const lines = content.split(`
6086
6300
  `);
@@ -6121,6 +6335,42 @@ function parseResolvedQuestions(content) {
6121
6335
  }
6122
6336
  return results;
6123
6337
  }
6338
+ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
6339
+ const filePath = `${dustPath}/tasks/${taskSlug}.md`;
6340
+ if (!fileSystem.exists(filePath)) {
6341
+ return null;
6342
+ }
6343
+ const content = await fileSystem.readFile(filePath);
6344
+ const titleMatch = content.match(/^#\s+(.+)$/m);
6345
+ if (!titleMatch) {
6346
+ return null;
6347
+ }
6348
+ const title = titleMatch[1].trim();
6349
+ const taskType = parseTaskType(content);
6350
+ let ideaTitle;
6351
+ let expedite;
6352
+ if (taskType === "implement") {
6353
+ expedite = true;
6354
+ ideaTitle = title.startsWith(EXPEDITE_IDEA_PREFIX) ? title.slice(EXPEDITE_IDEA_PREFIX.length) : title;
6355
+ } else if (taskType === "capture") {
6356
+ expedite = false;
6357
+ ideaTitle = title.startsWith(CAPTURE_IDEA_PREFIX) ? title.slice(CAPTURE_IDEA_PREFIX.length) : title;
6358
+ } else if (title.startsWith(EXPEDITE_IDEA_PREFIX)) {
6359
+ ideaTitle = title.slice(EXPEDITE_IDEA_PREFIX.length);
6360
+ expedite = true;
6361
+ } else if (title.startsWith(CAPTURE_IDEA_PREFIX)) {
6362
+ ideaTitle = title.slice(CAPTURE_IDEA_PREFIX.length);
6363
+ expedite = false;
6364
+ } else {
6365
+ return null;
6366
+ }
6367
+ const descriptionMatch = content.match(/^## Idea Description\n\n([\s\S]*?)\n\n## /m);
6368
+ if (!descriptionMatch) {
6369
+ return null;
6370
+ }
6371
+ const ideaDescription = descriptionMatch[1];
6372
+ return { ideaTitle, ideaDescription, expedite };
6373
+ }
6124
6374
 
6125
6375
  // lib/lint/validators/content-validator.ts
6126
6376
  var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
@@ -9261,6 +9511,34 @@ function executeMessageEffects(effects, dependencies) {
9261
9511
  }
9262
9512
  }
9263
9513
 
9514
+ // lib/bucket/bucket-dependencies.ts
9515
+ function createAuthFileSystem(dependencies) {
9516
+ return {
9517
+ exists: (path3) => {
9518
+ try {
9519
+ dependencies.accessSync(path3);
9520
+ return true;
9521
+ } catch {
9522
+ return false;
9523
+ }
9524
+ },
9525
+ isDirectory: (path3) => {
9526
+ try {
9527
+ return dependencies.statSync(path3).isDirectory();
9528
+ } catch {
9529
+ return false;
9530
+ }
9531
+ },
9532
+ getFileCreationTime: (path3) => dependencies.statSync(path3).birthtimeMs,
9533
+ readFile: (path3) => dependencies.readFile(path3, "utf8"),
9534
+ writeFile: (path3, content) => dependencies.writeFile(path3, content, "utf8"),
9535
+ mkdir: (path3, options) => dependencies.mkdir(path3, options).then(() => {}),
9536
+ readdir: dependencies.readdir.bind(dependencies),
9537
+ chmod: dependencies.chmod.bind(dependencies),
9538
+ rename: dependencies.rename.bind(dependencies)
9539
+ };
9540
+ }
9541
+
9264
9542
  // lib/bucket/native-io.ts
9265
9543
  import { spawn as nodeSpawn4 } from "node:child_process";
9266
9544
  import { EventEmitter } from "node:events";
@@ -9732,32 +10010,6 @@ function findRepoPathByRepositoryId(repositories, repositoryId) {
9732
10010
  return;
9733
10011
  }
9734
10012
  var DEFAULT_DUSTBUCKET_WS_URL = "wss://dustbucket.com/agent/connect";
9735
- function createAuthFileSystem(dependencies) {
9736
- return {
9737
- exists: (path3) => {
9738
- try {
9739
- dependencies.accessSync(path3);
9740
- return true;
9741
- } catch {
9742
- return false;
9743
- }
9744
- },
9745
- isDirectory: (path3) => {
9746
- try {
9747
- return dependencies.statSync(path3).isDirectory();
9748
- } catch {
9749
- return false;
9750
- }
9751
- },
9752
- getFileCreationTime: (path3) => dependencies.statSync(path3).birthtimeMs,
9753
- readFile: (path3) => dependencies.readFile(path3, "utf8"),
9754
- writeFile: (path3, content) => dependencies.writeFile(path3, content, "utf8"),
9755
- mkdir: (path3, options) => dependencies.mkdir(path3, options).then(() => {}),
9756
- readdir: dependencies.readdir.bind(dependencies),
9757
- chmod: dependencies.chmod.bind(dependencies),
9758
- rename: dependencies.rename.bind(dependencies)
9759
- };
9760
- }
9761
10013
  function createInitialState() {
9762
10014
  const sessionId = crypto.randomUUID();
9763
10015
  const systemBuffer = createLogBuffer();
@@ -10567,6 +10819,273 @@ Run \`dust bucket tool ${toolName}\` to see available operations.`);
10567
10819
  // lib/cli/commands/lint-markdown.ts
10568
10820
  import { isAbsolute, join as join11, relative, sep } from "node:path";
10569
10821
 
10822
+ // lib/artifacts/facts.ts
10823
+ async function parseFact(fileSystem, dustPath, slug) {
10824
+ const factPath = `${dustPath}/facts/${slug}.md`;
10825
+ if (!fileSystem.exists(factPath)) {
10826
+ throw new Error(`Fact not found: "${slug}" (expected file at ${factPath})`);
10827
+ }
10828
+ const content = await fileSystem.readFile(factPath);
10829
+ const title = extractTitle(content);
10830
+ if (!title) {
10831
+ throw new Error(`Fact file has no title: ${factPath}`);
10832
+ }
10833
+ return {
10834
+ slug,
10835
+ title,
10836
+ content
10837
+ };
10838
+ }
10839
+
10840
+ // lib/artifacts/ideas.ts
10841
+ function parseOpenQuestions(content) {
10842
+ const lines = content.split(`
10843
+ `);
10844
+ const questions = [];
10845
+ let inOpenQuestions = false;
10846
+ let currentQuestion = null;
10847
+ let currentOption = null;
10848
+ let descriptionLines = [];
10849
+ function flushOption() {
10850
+ if (currentOption) {
10851
+ currentOption.description = descriptionLines.join(`
10852
+ `).trim();
10853
+ descriptionLines = [];
10854
+ currentOption = null;
10855
+ }
10856
+ }
10857
+ function flushQuestion() {
10858
+ flushOption();
10859
+ if (currentQuestion) {
10860
+ questions.push(currentQuestion);
10861
+ currentQuestion = null;
10862
+ }
10863
+ }
10864
+ let inCodeFence = false;
10865
+ for (const line of lines) {
10866
+ if (line.startsWith("```")) {
10867
+ inCodeFence = !inCodeFence;
10868
+ if (currentOption) {
10869
+ descriptionLines.push(line);
10870
+ }
10871
+ continue;
10872
+ }
10873
+ if (inCodeFence) {
10874
+ if (currentOption) {
10875
+ descriptionLines.push(line);
10876
+ }
10877
+ continue;
10878
+ }
10879
+ if (line.startsWith("## ")) {
10880
+ if (inOpenQuestions) {
10881
+ flushQuestion();
10882
+ }
10883
+ inOpenQuestions = line.trimEnd() === "## Open Questions";
10884
+ continue;
10885
+ }
10886
+ if (!inOpenQuestions)
10887
+ continue;
10888
+ if (line.startsWith("### ")) {
10889
+ flushQuestion();
10890
+ currentQuestion = {
10891
+ question: line.slice(4).trim(),
10892
+ options: []
10893
+ };
10894
+ continue;
10895
+ }
10896
+ if (line.startsWith("#### ")) {
10897
+ flushOption();
10898
+ currentOption = {
10899
+ name: line.slice(5).trim(),
10900
+ description: ""
10901
+ };
10902
+ if (currentQuestion) {
10903
+ currentQuestion.options.push(currentOption);
10904
+ }
10905
+ continue;
10906
+ }
10907
+ if (currentOption) {
10908
+ descriptionLines.push(line);
10909
+ }
10910
+ }
10911
+ flushQuestion();
10912
+ return questions;
10913
+ }
10914
+ async function parseIdea(fileSystem, dustPath, slug) {
10915
+ const ideaPath = `${dustPath}/ideas/${slug}.md`;
10916
+ if (!fileSystem.exists(ideaPath)) {
10917
+ throw new Error(`Idea not found: "${slug}" (expected file at ${ideaPath})`);
10918
+ }
10919
+ const content = await fileSystem.readFile(ideaPath);
10920
+ const title = extractTitle(content);
10921
+ if (!title) {
10922
+ throw new Error(`Idea file has no title: ${ideaPath}`);
10923
+ }
10924
+ const openingSentence = extractOpeningSentence(content);
10925
+ const openQuestions = parseOpenQuestions(content);
10926
+ return {
10927
+ slug,
10928
+ title,
10929
+ openingSentence,
10930
+ content,
10931
+ openQuestions
10932
+ };
10933
+ }
10934
+
10935
+ // lib/artifacts/principles.ts
10936
+ function extractLinksFromSection(content, sectionHeading) {
10937
+ const lines = content.split(`
10938
+ `);
10939
+ const links = [];
10940
+ let inSection = false;
10941
+ for (const line of lines) {
10942
+ if (line.startsWith("## ")) {
10943
+ inSection = line.trimEnd() === `## ${sectionHeading}`;
10944
+ continue;
10945
+ }
10946
+ if (!inSection)
10947
+ continue;
10948
+ if (line.startsWith("# "))
10949
+ break;
10950
+ const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
10951
+ if (linkMatch) {
10952
+ const target = linkMatch[2];
10953
+ const slugMatch = target.match(/([^/]+)\.md$/);
10954
+ if (slugMatch) {
10955
+ links.push(slugMatch[1]);
10956
+ }
10957
+ }
10958
+ }
10959
+ return links;
10960
+ }
10961
+ function extractSingleLinkFromSection(content, sectionHeading) {
10962
+ const links = extractLinksFromSection(content, sectionHeading);
10963
+ return links.length === 1 ? links[0] : null;
10964
+ }
10965
+ async function parsePrinciple(fileSystem, dustPath, slug) {
10966
+ const principlePath = `${dustPath}/principles/${slug}.md`;
10967
+ if (!fileSystem.exists(principlePath)) {
10968
+ throw new Error(`Principle not found: "${slug}" (expected file at ${principlePath})`);
10969
+ }
10970
+ const content = await fileSystem.readFile(principlePath);
10971
+ const title = extractTitle(content) || slug;
10972
+ const parentPrinciple = extractSingleLinkFromSection(content, "Parent Principle");
10973
+ const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
10974
+ return {
10975
+ slug,
10976
+ title,
10977
+ content,
10978
+ parentPrinciple,
10979
+ subPrinciples
10980
+ };
10981
+ }
10982
+
10983
+ // lib/artifacts/tasks.ts
10984
+ function extractLinksFromSection2(content, sectionHeading) {
10985
+ const lines = content.split(`
10986
+ `);
10987
+ const links = [];
10988
+ let inSection = false;
10989
+ for (const line of lines) {
10990
+ if (line.startsWith("## ")) {
10991
+ inSection = line.trimEnd() === `## ${sectionHeading}`;
10992
+ continue;
10993
+ }
10994
+ if (!inSection)
10995
+ continue;
10996
+ if (line.startsWith("# "))
10997
+ break;
10998
+ const linkMatch = line.match(MARKDOWN_LINK_PATTERN);
10999
+ if (linkMatch) {
11000
+ const target = linkMatch[2];
11001
+ const slugMatch = target.match(/([^/]+)\.md$/);
11002
+ if (slugMatch) {
11003
+ links.push(slugMatch[1]);
11004
+ }
11005
+ }
11006
+ }
11007
+ return links;
11008
+ }
11009
+ function extractDefinitionOfDone(content) {
11010
+ const lines = content.split(`
11011
+ `);
11012
+ const items = [];
11013
+ let inSection = false;
11014
+ for (const line of lines) {
11015
+ if (line.startsWith("## ")) {
11016
+ inSection = line.trimEnd() === "## Definition of Done";
11017
+ continue;
11018
+ }
11019
+ if (!inSection)
11020
+ continue;
11021
+ if (line.startsWith("# "))
11022
+ break;
11023
+ const listMatch = line.match(/^-\s+(.+)$/);
11024
+ if (listMatch) {
11025
+ items.push(listMatch[1].trim());
11026
+ }
11027
+ }
11028
+ return items;
11029
+ }
11030
+ async function parseTask(fileSystem, dustPath, slug) {
11031
+ const taskPath = `${dustPath}/tasks/${slug}.md`;
11032
+ if (!fileSystem.exists(taskPath)) {
11033
+ throw new Error(`Task not found: "${slug}" (expected file at ${taskPath})`);
11034
+ }
11035
+ const content = await fileSystem.readFile(taskPath);
11036
+ const title = extractTitle(content);
11037
+ if (!title) {
11038
+ throw new Error(`Task file has no title: ${taskPath}`);
11039
+ }
11040
+ const principles = extractLinksFromSection2(content, "Principles");
11041
+ const blockedBy = extractLinksFromSection2(content, "Blocked By");
11042
+ const definitionOfDone = extractDefinitionOfDone(content);
11043
+ return {
11044
+ slug,
11045
+ title,
11046
+ content,
11047
+ principles,
11048
+ blockedBy,
11049
+ definitionOfDone
11050
+ };
11051
+ }
11052
+
11053
+ // lib/artifacts/repository-principle-hierarchy.ts
11054
+ function sortNodes(nodes) {
11055
+ nodes.sort((a, b) => a.title.localeCompare(b.title));
11056
+ for (const node of nodes) {
11057
+ sortNodes(node.children);
11058
+ }
11059
+ }
11060
+ async function getRepositoryPrincipleHierarchy(repository) {
11061
+ const slugs = await repository.listPrinciples();
11062
+ if (slugs.length === 0) {
11063
+ return [];
11064
+ }
11065
+ const principles = await Promise.all(slugs.map((slug) => repository.parsePrinciple({ slug })));
11066
+ const principleSet = new Set(slugs);
11067
+ const nodeBySlug = new Map;
11068
+ for (const p of principles) {
11069
+ nodeBySlug.set(p.slug, {
11070
+ slug: p.slug,
11071
+ title: p.title,
11072
+ children: []
11073
+ });
11074
+ }
11075
+ const roots = [];
11076
+ for (const p of principles) {
11077
+ const node = nodeBySlug.get(p.slug);
11078
+ const parentSlug = p.parentPrinciple;
11079
+ if (!parentSlug || !principleSet.has(parentSlug)) {
11080
+ roots.push(node);
11081
+ } else {
11082
+ nodeBySlug.get(parentSlug).children.push(node);
11083
+ }
11084
+ }
11085
+ sortNodes(roots);
11086
+ return roots;
11087
+ }
11088
+
10570
11089
  // lib/artifacts/index.ts
10571
11090
  var ARTIFACT_TYPES = [
10572
11091
  "facts",
@@ -10574,6 +11093,90 @@ var ARTIFACT_TYPES = [
10574
11093
  "principles",
10575
11094
  "tasks"
10576
11095
  ];
11096
+ function buildReadOperations(fileSystem, dustPath) {
11097
+ return {
11098
+ artifactPath(type, slug) {
11099
+ return `${dustPath}/${type}/${slug}.md`;
11100
+ },
11101
+ async parseIdea(options) {
11102
+ return parseIdea(fileSystem, dustPath, options.slug);
11103
+ },
11104
+ async listIdeas() {
11105
+ const ideasPath = `${dustPath}/ideas`;
11106
+ if (!fileSystem.exists(ideasPath)) {
11107
+ return [];
11108
+ }
11109
+ const files = await fileSystem.readdir(ideasPath);
11110
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
11111
+ },
11112
+ async parsePrinciple(options) {
11113
+ return parsePrinciple(fileSystem, dustPath, options.slug);
11114
+ },
11115
+ async listPrinciples() {
11116
+ const principlesPath = `${dustPath}/principles`;
11117
+ if (!fileSystem.exists(principlesPath)) {
11118
+ return [];
11119
+ }
11120
+ const files = await fileSystem.readdir(principlesPath);
11121
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
11122
+ },
11123
+ async parseFact(options) {
11124
+ return parseFact(fileSystem, dustPath, options.slug);
11125
+ },
11126
+ async listFacts() {
11127
+ const factsPath = `${dustPath}/facts`;
11128
+ if (!fileSystem.exists(factsPath)) {
11129
+ return [];
11130
+ }
11131
+ const files = await fileSystem.readdir(factsPath);
11132
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
11133
+ },
11134
+ async parseTask(options) {
11135
+ return parseTask(fileSystem, dustPath, options.slug);
11136
+ },
11137
+ async listTasks() {
11138
+ const tasksPath = `${dustPath}/tasks`;
11139
+ if (!fileSystem.exists(tasksPath)) {
11140
+ return [];
11141
+ }
11142
+ const files = await fileSystem.readdir(tasksPath);
11143
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
11144
+ },
11145
+ async findWorkflowTaskForIdea(options) {
11146
+ return findWorkflowTaskForIdea(fileSystem, dustPath, options.ideaSlug);
11147
+ },
11148
+ async parseCaptureIdeaTask(options) {
11149
+ return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
11150
+ },
11151
+ async buildTaskGraph() {
11152
+ const taskSlugs = await this.listTasks();
11153
+ const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
11154
+ const workflowTypeByTaskSlug = new Map;
11155
+ for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
11156
+ workflowTypeByTaskSlug.set(match.taskSlug, match.type);
11157
+ }
11158
+ const nodes = [];
11159
+ const edges = [];
11160
+ for (const slug of taskSlugs) {
11161
+ const task = await this.parseTask({ slug });
11162
+ nodes.push({
11163
+ task,
11164
+ workflowType: workflowTypeByTaskSlug.get(slug) ?? null
11165
+ });
11166
+ for (const blockerSlug of task.blockedBy) {
11167
+ edges.push({ from: blockerSlug, to: slug });
11168
+ }
11169
+ }
11170
+ return { nodes, edges };
11171
+ },
11172
+ async getRepositoryPrincipleHierarchy() {
11173
+ return getRepositoryPrincipleHierarchy(this);
11174
+ }
11175
+ };
11176
+ }
11177
+ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
11178
+ return buildReadOperations(fileSystem, dustPath);
11179
+ }
10577
11180
 
10578
11181
  // lib/lint/validators/directory-validator.ts
10579
11182
  var EXPECTED_DIRECTORIES = [...ARTIFACT_TYPES, "config"];
@@ -12916,10 +13519,10 @@ Surprising behavior erodes trust and slows people down. Unsurprising behavior le
12916
13519
  ];
12917
13520
 
12918
13521
  // lib/artifacts/core-principles.ts
12919
- function sortNodes(nodes) {
13522
+ function sortNodes2(nodes) {
12920
13523
  nodes.sort((a, b) => a.title.localeCompare(b.title));
12921
13524
  for (const node of nodes) {
12922
- sortNodes(node.children);
13525
+ sortNodes2(node.children);
12923
13526
  }
12924
13527
  }
12925
13528
  function isInternalPrinciple(principleContent) {
@@ -12963,12 +13566,12 @@ function getCorePrincipleTree(allPrinciples, config) {
12963
13566
  nodeBySlug.get(parentSlug).children.push(node);
12964
13567
  }
12965
13568
  }
12966
- sortNodes(roots);
13569
+ sortNodes2(roots);
12967
13570
  return roots;
12968
13571
  }
12969
13572
 
12970
13573
  // lib/core-principles.ts
12971
- function extractLinksFromSection(content, sectionHeading) {
13574
+ function extractLinksFromSection3(content, sectionHeading) {
12972
13575
  const lines = content.split(`
12973
13576
  `);
12974
13577
  const links = [];
@@ -12993,8 +13596,8 @@ function extractLinksFromSection(content, sectionHeading) {
12993
13596
  }
12994
13597
  return links;
12995
13598
  }
12996
- function extractSingleLinkFromSection(content, sectionHeading) {
12997
- const links = extractLinksFromSection(content, sectionHeading);
13599
+ function extractSingleLinkFromSection2(content, sectionHeading) {
13600
+ const links = extractLinksFromSection3(content, sectionHeading);
12998
13601
  return links.length === 1 ? links[0] : null;
12999
13602
  }
13000
13603
  function parsePrincipleContent(slug, content) {
@@ -13002,8 +13605,8 @@ function parsePrincipleContent(slug, content) {
13002
13605
  if (!title) {
13003
13606
  throw new Error(`Principle has no title: ${slug}`);
13004
13607
  }
13005
- const parentPrinciple = extractSingleLinkFromSection(content, "Parent Principle");
13006
- const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
13608
+ const parentPrinciple = extractSingleLinkFromSection2(content, "Parent Principle");
13609
+ const subPrinciples = extractLinksFromSection3(content, "Sub-Principles");
13007
13610
  return {
13008
13611
  slug,
13009
13612
  title,
@@ -13212,7 +13815,6 @@ async function init(dependencies) {
13212
13815
  }
13213
13816
 
13214
13817
  // lib/cli/commands/list.ts
13215
- import { basename as basename3 } from "node:path";
13216
13818
  function workflowTypeToStatus(type) {
13217
13819
  switch (type) {
13218
13820
  case "refine":
@@ -13282,41 +13884,7 @@ function formatPrinciplesSection(header, entries) {
13282
13884
  }
13283
13885
  return lines;
13284
13886
  }
13285
- async function buildPrincipleHierarchy(principlesPath, fileSystem) {
13286
- const files = await fileSystem.readdir(principlesPath);
13287
- const mdFiles = files.filter((f) => f.endsWith(".md"));
13288
- const relationships = [];
13289
- const titleMap = new Map;
13290
- for (const file of mdFiles) {
13291
- const filePath = `${principlesPath}/${file}`;
13292
- const content = await fileSystem.readFile(filePath);
13293
- const artifact = parseArtifact(filePath, content);
13294
- relationships.push(extractPrincipleRelationships(artifact));
13295
- const title = extractTitle(content) || basename3(file, ".md");
13296
- titleMap.set(filePath, title);
13297
- }
13298
- const relMap = new Map;
13299
- for (const rel of relationships) {
13300
- relMap.set(rel.filePath, rel);
13301
- }
13302
- const rootPrinciples = relationships.filter((rel) => rel.parentPrinciples.length === 0);
13303
- function buildNode(filePath) {
13304
- const rel = relMap.get(filePath);
13305
- const children = [];
13306
- if (rel) {
13307
- for (const childPath of rel.subPrinciples) {
13308
- children.push(buildNode(childPath));
13309
- }
13310
- }
13311
- return {
13312
- filePath,
13313
- title: titleMap.get(filePath) || basename3(filePath, ".md"),
13314
- children
13315
- };
13316
- }
13317
- return rootPrinciples.map((rel) => buildNode(rel.filePath));
13318
- }
13319
- function renderHierarchy(nodes, output, prefix = "") {
13887
+ function renderRepositoryPrincipleHierarchy(nodes, output, prefix = "") {
13320
13888
  for (let i = 0;i < nodes.length; i++) {
13321
13889
  const node = nodes[i];
13322
13890
  const isLastNode = i === nodes.length - 1;
@@ -13324,7 +13892,7 @@ function renderHierarchy(nodes, output, prefix = "") {
13324
13892
  const childPrefix = isLastNode ? " " : "│ ";
13325
13893
  output(`${prefix}${connector}${node.title}`);
13326
13894
  if (node.children.length > 0) {
13327
- renderHierarchy(node.children, output, prefix + childPrefix);
13895
+ renderRepositoryPrincipleHierarchy(node.children, output, prefix + childPrefix);
13328
13896
  }
13329
13897
  }
13330
13898
  }
@@ -13434,9 +14002,10 @@ async function processPrinciplesList(context) {
13434
14002
  stdout("");
13435
14003
  }
13436
14004
  if (hasLocalPrinciples) {
13437
- const localHierarchy = await buildPrincipleHierarchy(localDirPath, fileSystem);
14005
+ const repository = buildReadOnlyArtifactsRepository(fileSystem, dustPath);
14006
+ const localHierarchy = await repository.getRepositoryPrincipleHierarchy();
13438
14007
  stdout(`${colors.bold}Local${colors.reset}`);
13439
- renderHierarchy(localHierarchy, (line) => stdout(line));
14008
+ renderRepositoryPrincipleHierarchy(localHierarchy, (line) => stdout(line));
13440
14009
  stdout("");
13441
14010
  const collectedItems = [];
13442
14011
  for (const file of localMdFiles) {