@joshski/dust 0.1.110 → 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/README.md CHANGED
@@ -47,3 +47,7 @@ Details live in the [.dust/facts](./.dust/facts) directory:
47
47
  - [Directory Structure](./.dust/facts/dust-directory-structure.md) — how `.dust/` is organized
48
48
  - [Configuration](./.dust/facts/configuration-system.md) — settings and quality checks
49
49
  - [CLI Commands](./.dust/facts/unified-cli.md) — full command reference
50
+
51
+ ## Dust Bucket Worker
52
+
53
+ The `dust bucket worker` command runs a background worker that syncs agent sessions to [dustbucket.com](https://dustbucket.com). This requires a dustbucket.com account (currently in private alpha, invite only).
@@ -1,9 +1,6 @@
1
1
  import type { FileReader } from '../filesystem/types';
2
- export interface Fact {
3
- slug: string;
4
- title: string;
5
- content: string;
6
- }
2
+ import type { Fact } from './types';
3
+ export type { Fact };
7
4
  /**
8
5
  * Parses a fact markdown file into a structured Fact object.
9
6
  */
@@ -1,19 +1,6 @@
1
1
  import type { FileReader } from '../filesystem/types';
2
- export interface IdeaOption {
3
- name: string;
4
- description: string;
5
- }
6
- export interface IdeaOpenQuestion {
7
- question: string;
8
- options: IdeaOption[];
9
- }
10
- export interface Idea {
11
- slug: string;
12
- title: string;
13
- openingSentence: string | null;
14
- content: string;
15
- openQuestions: IdeaOpenQuestion[];
16
- }
2
+ import type { Idea, IdeaOpenQuestion, IdeaOption } from './types';
3
+ export type { Idea, IdeaOpenQuestion, IdeaOption };
17
4
  export interface ParsedIdeaContent {
18
5
  title: string | null;
19
6
  body: string;
@@ -6,21 +6,11 @@ import { type Principle } from './principles';
6
6
  import { type Task } from './tasks';
7
7
  import { type ParsedArtifact, type ParsedMarkdownLink, type ParsedSection, parseArtifact } from './parsed-artifact';
8
8
  import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, parseResolvedQuestions, type TaskType, type WorkflowTaskMatch } from './workflow-tasks';
9
+ import type { ArtifactType, ReadOnlyArtifactsRepository } from './types';
10
+ export type { ArtifactType, ReadOnlyArtifactsRepository, RepositoryPrincipleNode, TaskGraph, TaskGraphNode, } from './types';
9
11
  export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedArtifact, ParsedCaptureIdeaTask, ParsedIdeaContent, ParsedMarkdownLink, ParsedSection, Principle, Task, TaskType, WorkflowTaskMatch, };
10
- export interface TaskGraphNode {
11
- task: Task;
12
- workflowType: TaskType | null;
13
- }
14
- export interface TaskGraph {
15
- nodes: TaskGraphNode[];
16
- edges: Array<{
17
- from: string;
18
- to: string;
19
- }>;
20
- }
21
12
  export { CAPTURE_IDEA_PREFIX, extractTitle, findAllWorkflowTasks, ideaContentToMarkdown, parseArtifact, parseIdeaContent, parseOpenQuestions, parseResolvedQuestions, };
22
13
  export type { IdeaInProgress };
23
- export type ArtifactType = 'ideas' | 'tasks' | 'principles' | 'facts';
24
14
  export declare const ARTIFACT_TYPES: ArtifactType[];
25
15
  export declare const DUST_PATH_PREFIX = ".dust/";
26
16
  /**
@@ -32,32 +22,6 @@ export declare function parseArtifactPath(path: string): {
32
22
  type: ArtifactType;
33
23
  slug: string;
34
24
  } | null;
35
- export interface ReadOnlyArtifactsRepository {
36
- artifactPath(type: ArtifactType, slug: string): string;
37
- parseIdea(options: {
38
- slug: string;
39
- }): Promise<Idea>;
40
- listIdeas(): Promise<string[]>;
41
- parsePrinciple(options: {
42
- slug: string;
43
- }): Promise<Principle>;
44
- listPrinciples(): Promise<string[]>;
45
- parseFact(options: {
46
- slug: string;
47
- }): Promise<Fact>;
48
- listFacts(): Promise<string[]>;
49
- parseTask(options: {
50
- slug: string;
51
- }): Promise<Task>;
52
- listTasks(): Promise<string[]>;
53
- findWorkflowTaskForIdea(options: {
54
- ideaSlug: string;
55
- }): Promise<WorkflowTaskMatch | null>;
56
- parseCaptureIdeaTask(options: {
57
- taskSlug: string;
58
- }): Promise<ParsedCaptureIdeaTask | null>;
59
- buildTaskGraph(): Promise<TaskGraph>;
60
- }
61
25
  export interface ArtifactsRepository extends ReadOnlyArtifactsRepository {
62
26
  createRefineIdeaTask(options: {
63
27
  ideaSlug: string;
@@ -1,11 +1,6 @@
1
1
  import type { FileReader } from '../filesystem/types';
2
- export interface Principle {
3
- slug: string;
4
- title: string;
5
- content: string;
6
- parentPrinciple: string | null;
7
- subPrinciples: string[];
8
- }
2
+ import type { Principle } from './types';
3
+ export type { Principle };
9
4
  /**
10
5
  * Parses a principle markdown file into a structured Principle object.
11
6
  */
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Repository Principle Hierarchy API
3
+ *
4
+ * Provides pure functional access to build hierarchical trees of principles
5
+ * from a repository's .dust/principles/ directory.
6
+ */
7
+ import type { ReadOnlyArtifactsRepository, RepositoryPrincipleNode } from './types';
8
+ /**
9
+ * Builds a hierarchy tree of repository principles from .dust/principles/.
10
+ * Returns root nodes (principles with no parent or filtered parent).
11
+ * Children are sorted alphabetically by title (recursive).
12
+ *
13
+ * @param repository - The repository to read principles from
14
+ * @returns Array of root principle nodes, empty array if no principles exist
15
+ */
16
+ export declare function getRepositoryPrincipleHierarchy(repository: ReadOnlyArtifactsRepository): Promise<RepositoryPrincipleNode[]>;
@@ -1,12 +1,6 @@
1
1
  import type { FileReader } from '../filesystem/types';
2
- export interface Task {
3
- slug: string;
4
- title: string;
5
- content: string;
6
- principles: string[];
7
- blockedBy: string[];
8
- definitionOfDone: string[];
9
- }
2
+ import type { Task } from './types';
3
+ export type { Task };
10
4
  /**
11
5
  * Parses a task markdown file into a structured Task object.
12
6
  */
@@ -0,0 +1,98 @@
1
+ export type TaskType = 'implement' | 'capture' | 'refine' | 'decompose' | 'shelve';
2
+ export interface Fact {
3
+ slug: string;
4
+ title: string;
5
+ content: string;
6
+ }
7
+ export interface IdeaOption {
8
+ name: string;
9
+ description: string;
10
+ }
11
+ export interface IdeaOpenQuestion {
12
+ question: string;
13
+ options: IdeaOption[];
14
+ }
15
+ export interface Idea {
16
+ slug: string;
17
+ title: string;
18
+ openingSentence: string | null;
19
+ content: string;
20
+ openQuestions: IdeaOpenQuestion[];
21
+ }
22
+ export interface Principle {
23
+ slug: string;
24
+ title: string;
25
+ content: string;
26
+ parentPrinciple: string | null;
27
+ subPrinciples: string[];
28
+ }
29
+ export interface Task {
30
+ slug: string;
31
+ title: string;
32
+ content: string;
33
+ principles: string[];
34
+ blockedBy: string[];
35
+ definitionOfDone: string[];
36
+ }
37
+ export interface OpenQuestionResponse {
38
+ question: string;
39
+ chosenOption: string;
40
+ }
41
+ export interface WorkflowTaskMatch {
42
+ type: TaskType;
43
+ ideaSlug: string;
44
+ taskSlug: string;
45
+ resolvedQuestions: OpenQuestionResponse[];
46
+ }
47
+ export interface ParsedCaptureIdeaTask {
48
+ ideaTitle: string;
49
+ ideaDescription: string;
50
+ expedite: boolean;
51
+ }
52
+ export type ArtifactType = 'ideas' | 'tasks' | 'principles' | 'facts';
53
+ export interface TaskGraphNode {
54
+ task: Task;
55
+ workflowType: TaskType | null;
56
+ }
57
+ export interface TaskGraph {
58
+ nodes: TaskGraphNode[];
59
+ edges: Array<{
60
+ from: string;
61
+ to: string;
62
+ }>;
63
+ }
64
+ /**
65
+ * Node in the principle hierarchy tree
66
+ */
67
+ export interface RepositoryPrincipleNode {
68
+ slug: string;
69
+ title: string;
70
+ children: RepositoryPrincipleNode[];
71
+ }
72
+ export interface ReadOnlyArtifactsRepository {
73
+ artifactPath(type: ArtifactType, slug: string): string;
74
+ parseIdea(options: {
75
+ slug: string;
76
+ }): Promise<Idea>;
77
+ listIdeas(): Promise<string[]>;
78
+ parsePrinciple(options: {
79
+ slug: string;
80
+ }): Promise<Principle>;
81
+ listPrinciples(): Promise<string[]>;
82
+ parseFact(options: {
83
+ slug: string;
84
+ }): Promise<Fact>;
85
+ listFacts(): Promise<string[]>;
86
+ parseTask(options: {
87
+ slug: string;
88
+ }): Promise<Task>;
89
+ listTasks(): Promise<string[]>;
90
+ findWorkflowTaskForIdea(options: {
91
+ ideaSlug: string;
92
+ }): Promise<WorkflowTaskMatch | null>;
93
+ parseCaptureIdeaTask(options: {
94
+ taskSlug: string;
95
+ }): Promise<ParsedCaptureIdeaTask | null>;
96
+ buildTaskGraph(): Promise<TaskGraph>;
97
+ getRepositoryPrincipleHierarchy(): Promise<RepositoryPrincipleNode[]>;
98
+ }
@@ -1,4 +1,6 @@
1
1
  import type { FileSystem, ReadableFileSystem } from '../filesystem/types';
2
+ import type { OpenQuestionResponse, ParsedCaptureIdeaTask, TaskType, WorkflowTaskMatch } from './types';
3
+ export type { OpenQuestionResponse, ParsedCaptureIdeaTask, TaskType, WorkflowTaskMatch, };
2
4
  export declare const IDEA_TRANSITION_PREFIXES: string[];
3
5
  export declare const CAPTURE_IDEA_PREFIX = "Add Idea: ";
4
6
  export declare const EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
@@ -6,11 +8,6 @@ export interface IdeaInProgress {
6
8
  taskSlug: string;
7
9
  ideaTitle: string;
8
10
  }
9
- export interface ParsedCaptureIdeaTask {
10
- ideaTitle: string;
11
- ideaDescription: string;
12
- expedite: boolean;
13
- }
14
11
  /**
15
12
  * Converts a markdown title to the expected filename using deterministic rules:
16
13
  * 1. Convert to lowercase
@@ -22,18 +19,11 @@ export interface ParsedCaptureIdeaTask {
22
19
  */
23
20
  export declare function titleToFilename(title: string): string;
24
21
  export declare const VALID_TASK_TYPES: readonly ["implement", "capture", "refine", "decompose", "shelve"];
25
- export type TaskType = (typeof VALID_TASK_TYPES)[number];
26
22
  /**
27
23
  * Extracts and validates the task type from the ## Task Type section.
28
24
  * Returns the task type if found and valid, null otherwise.
29
25
  */
30
26
  export declare function parseTaskType(content: string): TaskType | null;
31
- export interface WorkflowTaskMatch {
32
- type: TaskType;
33
- ideaSlug: string;
34
- taskSlug: string;
35
- resolvedQuestions: OpenQuestionResponse[];
36
- }
37
27
  export interface AllWorkflowTasks {
38
28
  captureIdeaTasks: IdeaInProgress[];
39
29
  workflowTasksByIdeaSlug: Map<string, WorkflowTaskMatch>;
@@ -43,10 +33,6 @@ export declare function findWorkflowTaskForIdea(fileSystem: ReadableFileSystem,
43
33
  export interface CreateIdeaTransitionTaskResult {
44
34
  filePath: string;
45
35
  }
46
- export interface OpenQuestionResponse {
47
- question: string;
48
- chosenOption: string;
49
- }
50
36
  export interface DecomposeIdeaOptions {
51
37
  ideaSlug: string;
52
38
  description?: string;
package/dist/artifacts.js CHANGED
@@ -294,10 +294,7 @@ async function parsePrinciple(fileSystem, dustPath, slug) {
294
294
  throw new Error(`Principle not found: "${slug}" (expected file at ${principlePath})`);
295
295
  }
296
296
  const content = await fileSystem.readFile(principlePath);
297
- const title = extractTitle(content);
298
- if (!title) {
299
- throw new Error(`Principle file has no title: ${principlePath}`);
300
- }
297
+ const title = extractTitle(content) || slug;
301
298
  const parentPrinciple = extractSingleLinkFromSection(content, "Parent Principle");
302
299
  const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
303
300
  return {
@@ -999,6 +996,42 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
999
996
  return { ideaTitle, ideaDescription, expedite };
1000
997
  }
1001
998
 
999
+ // lib/artifacts/repository-principle-hierarchy.ts
1000
+ function sortNodes(nodes) {
1001
+ nodes.sort((a, b) => a.title.localeCompare(b.title));
1002
+ for (const node of nodes) {
1003
+ sortNodes(node.children);
1004
+ }
1005
+ }
1006
+ async function getRepositoryPrincipleHierarchy(repository) {
1007
+ const slugs = await repository.listPrinciples();
1008
+ if (slugs.length === 0) {
1009
+ return [];
1010
+ }
1011
+ const principles = await Promise.all(slugs.map((slug) => repository.parsePrinciple({ slug })));
1012
+ const principleSet = new Set(slugs);
1013
+ const nodeBySlug = new Map;
1014
+ for (const p of principles) {
1015
+ nodeBySlug.set(p.slug, {
1016
+ slug: p.slug,
1017
+ title: p.title,
1018
+ children: []
1019
+ });
1020
+ }
1021
+ const roots = [];
1022
+ for (const p of principles) {
1023
+ const node = nodeBySlug.get(p.slug);
1024
+ const parentSlug = p.parentPrinciple;
1025
+ if (!parentSlug || !principleSet.has(parentSlug)) {
1026
+ roots.push(node);
1027
+ } else {
1028
+ nodeBySlug.get(parentSlug).children.push(node);
1029
+ }
1030
+ }
1031
+ sortNodes(roots);
1032
+ return roots;
1033
+ }
1034
+
1002
1035
  // lib/artifacts/index.ts
1003
1036
  var ARTIFACT_TYPES = [
1004
1037
  "facts",
@@ -1099,6 +1132,9 @@ function buildReadOperations(fileSystem, dustPath) {
1099
1132
  }
1100
1133
  }
1101
1134
  return { nodes, edges };
1135
+ },
1136
+ async getRepositoryPrincipleHierarchy() {
1137
+ return getRepositoryPrincipleHierarchy(this);
1102
1138
  }
1103
1139
  };
1104
1140
  }
@@ -12,7 +12,7 @@ import type { SendAgentEventFn } from '../loop/wire-events';
12
12
  import { type RunnerDependencies as CodexRunnerDependencies, run as codexRun } from '../codex/run';
13
13
  import type { SendEventFn } from './events';
14
14
  import { type LogBuffer } from './log-buffer';
15
- import type { RepositoryDependencies, RepositoryState } from './repository';
15
+ import type { RepositoryDependencies, RepositoryState } from './repository-types';
16
16
  /**
17
17
  * Create stdout/stderr callbacks that append to a log buffer.
18
18
  * Extracted for testability (v8 coverage limitation on inline callbacks).
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared types for repository management.
3
+ *
4
+ * Extracted to break the cyclic dependency between repository.ts and repository-loop.ts.
5
+ */
6
+ import { spawn as nodeSpawn } from 'node:child_process';
7
+ import type { run as claudeRun } from '../claude/run';
8
+ import type { FileSystem } from '../cli/types';
9
+ import type { DockerDependencies } from '../docker/docker-agent';
10
+ import type { AuthConfig, RuntimeConfig, SessionConfig } from '../env-config';
11
+ import type { ToolExecutionRequest, ToolExecutionResult } from './command-events-proxy';
12
+ import type { LogBuffer } from './log-buffer';
13
+ import type { RepositoryLifecycleState } from './repository-lifecycle';
14
+ import type { ToolDefinition } from './server-messages';
15
+ export interface Repository {
16
+ name: string;
17
+ gitUrl: string;
18
+ gitSshUrl: string;
19
+ url: string;
20
+ id: number;
21
+ agentProvider?: string;
22
+ branch?: string;
23
+ }
24
+ export interface RepositoryState {
25
+ repository: Repository;
26
+ path: string;
27
+ logBuffer: LogBuffer;
28
+ lifecycle: RepositoryLifecycleState;
29
+ agentStatus: 'idle' | 'busy';
30
+ wakeUp?: () => void;
31
+ taskAvailablePending?: boolean;
32
+ }
33
+ export interface RepositoryDependencies {
34
+ spawn: typeof nodeSpawn;
35
+ run: typeof claudeRun;
36
+ fileSystem: FileSystem;
37
+ sleep: (ms: number) => Promise<void>;
38
+ getReposDir: () => string;
39
+ session: SessionConfig;
40
+ runtime: RuntimeConfig;
41
+ auth: AuthConfig;
42
+ /** Optional overrides for Docker dependency functions (for testing) */
43
+ dockerDeps?: Partial<DockerDependencies>;
44
+ /** Function to get current tool definitions */
45
+ getTools?: () => ToolDefinition[];
46
+ /** Function to get revealed tool families (for progressive disclosure) */
47
+ getRevealedFamilies?: () => Set<string>;
48
+ /** Forward tool execution requests to the bucket server */
49
+ forwardToolExecution?: (request: ToolExecutionRequest) => Promise<ToolExecutionResult>;
50
+ /** Mark a tool family as revealed (for progressive disclosure) */
51
+ revealFamily?: (familyName: string) => void;
52
+ /** Shell runner for pre-flight commands (install, check) */
53
+ shellRunner?: import('../cli/process-runner').ShellRunner;
54
+ /** Force Docker mode using bundled default Dockerfile */
55
+ forceDocker?: boolean;
56
+ /** Force Apple Container mode using bundled default Dockerfile */
57
+ forceAppleContainer?: boolean;
58
+ }
@@ -4,37 +4,13 @@
4
4
  * Git operations live in repository-git.ts.
5
5
  * Loop orchestration lives in repository-loop.ts.
6
6
  */
7
- import { spawn as nodeSpawn } from 'node:child_process';
8
- import { run as claudeRun } from '../claude/run';
9
7
  import type { CommandDependencies, FileSystem } from '../cli/types';
10
- import type { DockerDependencies } from '../docker/docker-agent';
11
- import { type AuthConfig, type RuntimeConfig, type SessionConfig } from '../env-config';
12
- import type { ToolExecutionRequest, ToolExecutionResult } from './command-events-proxy';
13
8
  import { type BucketEmitFn, type SendEventFn } from './events';
14
9
  import { type LogBuffer } from './log-buffer';
15
- import { type RepositoryLifecycleState } from './repository-lifecycle';
16
- import type { ToolDefinition } from './server-messages';
10
+ import type { Repository, RepositoryDependencies, RepositoryState } from './repository-types';
17
11
  export { cloneRepository, getRepoPath, removeRepository, } from './repository-git';
18
12
  export { runRepositoryLoop } from './repository-loop';
19
- export type { RepositoryLifecycleState } from './repository-lifecycle';
20
- export interface Repository {
21
- name: string;
22
- gitUrl: string;
23
- gitSshUrl: string;
24
- url: string;
25
- id: number;
26
- agentProvider?: string;
27
- branch?: string;
28
- }
29
- export interface RepositoryState {
30
- repository: Repository;
31
- path: string;
32
- logBuffer: LogBuffer;
33
- lifecycle: RepositoryLifecycleState;
34
- agentStatus: 'idle' | 'busy';
35
- wakeUp?: () => void;
36
- taskAvailablePending?: boolean;
37
- }
13
+ export type { Repository, RepositoryDependencies, RepositoryState, } from './repository-types';
38
14
  /**
39
15
  * Interface for the subset of bucket state needed by repository management.
40
16
  * Avoids circular dependency between repository.ts and bucket.ts.
@@ -46,32 +22,6 @@ export interface RepositoryManager {
46
22
  sendEvent: SendEventFn;
47
23
  sessionId: string;
48
24
  }
49
- export interface RepositoryDependencies {
50
- spawn: typeof nodeSpawn;
51
- run: typeof claudeRun;
52
- fileSystem: FileSystem;
53
- sleep: (ms: number) => Promise<void>;
54
- getReposDir: () => string;
55
- session: SessionConfig;
56
- runtime: RuntimeConfig;
57
- auth: AuthConfig;
58
- /** Optional overrides for Docker dependency functions (for testing) */
59
- dockerDeps?: Partial<DockerDependencies>;
60
- /** Function to get current tool definitions */
61
- getTools?: () => ToolDefinition[];
62
- /** Function to get revealed tool families (for progressive disclosure) */
63
- getRevealedFamilies?: () => Set<string>;
64
- /** Forward tool execution requests to the bucket server */
65
- forwardToolExecution?: (request: ToolExecutionRequest) => Promise<ToolExecutionResult>;
66
- /** Mark a tool family as revealed (for progressive disclosure) */
67
- revealFamily?: (familyName: string) => void;
68
- /** Shell runner for pre-flight commands (install, check) */
69
- shellRunner?: import('../cli/process-runner').ShellRunner;
70
- /** Force Docker mode using bundled default Dockerfile */
71
- forceDocker?: boolean;
72
- /** Force Apple Container mode using bundled default Dockerfile */
73
- forceAppleContainer?: boolean;
74
- }
75
25
  /**
76
26
  * Handle loop completion: transition lifecycle and reset agent status.
77
27
  * Extracted as a named function for testability.
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Typed server-to-client WebSocket messages for the bucket protocol.
3
3
  */
4
- import type { Repository } from './repository';
5
4
  import type { AgentCapability } from './agent-capabilities';
6
5
  export interface RepositoryListMessage {
7
6
  type: 'repository-list';
8
7
  repositories: RepositoryListItem[];
9
8
  }
10
- export interface RepositoryListItem extends Repository {
9
+ export interface RepositoryListItem {
10
+ name: string;
11
+ gitUrl: string;
12
+ gitSshUrl: string;
13
+ url: string;
14
+ id: number;
15
+ agentProvider?: string;
16
+ branch?: string;
11
17
  hasTask: boolean;
12
18
  }
13
19
  export interface TaskAvailableMessage {
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.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.110";
724
+ var DUST_VERSION = "0.1.111";
725
725
 
726
726
  // lib/cli/middleware.ts
727
727
  function applyMiddleware(middlewares, execute) {
@@ -6246,6 +6246,55 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
6246
6246
  }
6247
6247
  return { captureIdeaTasks, workflowTasksByIdeaSlug };
6248
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
+ }
6249
6298
  function parseResolvedQuestions(content) {
6250
6299
  const lines = content.split(`
6251
6300
  `);
@@ -6286,6 +6335,42 @@ function parseResolvedQuestions(content) {
6286
6335
  }
6287
6336
  return results;
6288
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
+ }
6289
6374
 
6290
6375
  // lib/lint/validators/content-validator.ts
6291
6376
  var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
@@ -9426,6 +9511,34 @@ function executeMessageEffects(effects, dependencies) {
9426
9511
  }
9427
9512
  }
9428
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
+
9429
9542
  // lib/bucket/native-io.ts
9430
9543
  import { spawn as nodeSpawn4 } from "node:child_process";
9431
9544
  import { EventEmitter } from "node:events";
@@ -9897,32 +10010,6 @@ function findRepoPathByRepositoryId(repositories, repositoryId) {
9897
10010
  return;
9898
10011
  }
9899
10012
  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
10013
  function createInitialState() {
9927
10014
  const sessionId = crypto.randomUUID();
9928
10015
  const systemBuffer = createLogBuffer();
@@ -10732,6 +10819,273 @@ Run \`dust bucket tool ${toolName}\` to see available operations.`);
10732
10819
  // lib/cli/commands/lint-markdown.ts
10733
10820
  import { isAbsolute, join as join11, relative, sep } from "node:path";
10734
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
+
10735
11089
  // lib/artifacts/index.ts
10736
11090
  var ARTIFACT_TYPES = [
10737
11091
  "facts",
@@ -10739,6 +11093,90 @@ var ARTIFACT_TYPES = [
10739
11093
  "principles",
10740
11094
  "tasks"
10741
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
+ }
10742
11180
 
10743
11181
  // lib/lint/validators/directory-validator.ts
10744
11182
  var EXPECTED_DIRECTORIES = [...ARTIFACT_TYPES, "config"];
@@ -13081,10 +13519,10 @@ Surprising behavior erodes trust and slows people down. Unsurprising behavior le
13081
13519
  ];
13082
13520
 
13083
13521
  // lib/artifacts/core-principles.ts
13084
- function sortNodes(nodes) {
13522
+ function sortNodes2(nodes) {
13085
13523
  nodes.sort((a, b) => a.title.localeCompare(b.title));
13086
13524
  for (const node of nodes) {
13087
- sortNodes(node.children);
13525
+ sortNodes2(node.children);
13088
13526
  }
13089
13527
  }
13090
13528
  function isInternalPrinciple(principleContent) {
@@ -13128,12 +13566,12 @@ function getCorePrincipleTree(allPrinciples, config) {
13128
13566
  nodeBySlug.get(parentSlug).children.push(node);
13129
13567
  }
13130
13568
  }
13131
- sortNodes(roots);
13569
+ sortNodes2(roots);
13132
13570
  return roots;
13133
13571
  }
13134
13572
 
13135
13573
  // lib/core-principles.ts
13136
- function extractLinksFromSection(content, sectionHeading) {
13574
+ function extractLinksFromSection3(content, sectionHeading) {
13137
13575
  const lines = content.split(`
13138
13576
  `);
13139
13577
  const links = [];
@@ -13158,8 +13596,8 @@ function extractLinksFromSection(content, sectionHeading) {
13158
13596
  }
13159
13597
  return links;
13160
13598
  }
13161
- function extractSingleLinkFromSection(content, sectionHeading) {
13162
- const links = extractLinksFromSection(content, sectionHeading);
13599
+ function extractSingleLinkFromSection2(content, sectionHeading) {
13600
+ const links = extractLinksFromSection3(content, sectionHeading);
13163
13601
  return links.length === 1 ? links[0] : null;
13164
13602
  }
13165
13603
  function parsePrincipleContent(slug, content) {
@@ -13167,8 +13605,8 @@ function parsePrincipleContent(slug, content) {
13167
13605
  if (!title) {
13168
13606
  throw new Error(`Principle has no title: ${slug}`);
13169
13607
  }
13170
- const parentPrinciple = extractSingleLinkFromSection(content, "Parent Principle");
13171
- const subPrinciples = extractLinksFromSection(content, "Sub-Principles");
13608
+ const parentPrinciple = extractSingleLinkFromSection2(content, "Parent Principle");
13609
+ const subPrinciples = extractLinksFromSection3(content, "Sub-Principles");
13172
13610
  return {
13173
13611
  slug,
13174
13612
  title,
@@ -13377,7 +13815,6 @@ async function init(dependencies) {
13377
13815
  }
13378
13816
 
13379
13817
  // lib/cli/commands/list.ts
13380
- import { basename as basename3 } from "node:path";
13381
13818
  function workflowTypeToStatus(type) {
13382
13819
  switch (type) {
13383
13820
  case "refine":
@@ -13447,41 +13884,7 @@ function formatPrinciplesSection(header, entries) {
13447
13884
  }
13448
13885
  return lines;
13449
13886
  }
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 = "") {
13887
+ function renderRepositoryPrincipleHierarchy(nodes, output, prefix = "") {
13485
13888
  for (let i = 0;i < nodes.length; i++) {
13486
13889
  const node = nodes[i];
13487
13890
  const isLastNode = i === nodes.length - 1;
@@ -13489,7 +13892,7 @@ function renderHierarchy(nodes, output, prefix = "") {
13489
13892
  const childPrefix = isLastNode ? " " : "│ ";
13490
13893
  output(`${prefix}${connector}${node.title}`);
13491
13894
  if (node.children.length > 0) {
13492
- renderHierarchy(node.children, output, prefix + childPrefix);
13895
+ renderRepositoryPrincipleHierarchy(node.children, output, prefix + childPrefix);
13493
13896
  }
13494
13897
  }
13495
13898
  }
@@ -13599,9 +14002,10 @@ async function processPrinciplesList(context) {
13599
14002
  stdout("");
13600
14003
  }
13601
14004
  if (hasLocalPrinciples) {
13602
- const localHierarchy = await buildPrincipleHierarchy(localDirPath, fileSystem);
14005
+ const repository = buildReadOnlyArtifactsRepository(fileSystem, dustPath);
14006
+ const localHierarchy = await repository.getRepositoryPrincipleHierarchy();
13603
14007
  stdout(`${colors.bold}Local${colors.reset}`);
13604
- renderHierarchy(localHierarchy, (line) => stdout(line));
14008
+ renderRepositoryPrincipleHierarchy(localHierarchy, (line) => stdout(line));
13605
14009
  stdout("");
13606
14010
  const collectedItems = [];
13607
14011
  for (const file of localMdFiles) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {