@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 +4 -0
- package/dist/artifacts/facts.d.ts +2 -5
- package/dist/artifacts/ideas.d.ts +2 -15
- package/dist/artifacts/index.d.ts +2 -38
- package/dist/artifacts/principles.d.ts +2 -7
- package/dist/artifacts/repository-principle-hierarchy.d.ts +16 -0
- package/dist/artifacts/tasks.d.ts +2 -8
- package/dist/artifacts/types.d.ts +98 -0
- package/dist/artifacts/workflow-tasks.d.ts +2 -16
- package/dist/artifacts.js +40 -4
- package/dist/bucket/repository-loop.d.ts +1 -1
- package/dist/bucket/repository-types.d.ts +58 -0
- package/dist/bucket/repository.d.ts +2 -52
- package/dist/bucket/server-messages.d.ts +8 -2
- package/dist/dust.js +479 -75
- package/package.json +1 -1
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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
|
-
|
|
3
|
-
|
|
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 {
|
|
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 {
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
13522
|
+
function sortNodes2(nodes) {
|
|
13085
13523
|
nodes.sort((a, b) => a.title.localeCompare(b.title));
|
|
13086
13524
|
for (const node of nodes) {
|
|
13087
|
-
|
|
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
|
-
|
|
13569
|
+
sortNodes2(roots);
|
|
13132
13570
|
return roots;
|
|
13133
13571
|
}
|
|
13134
13572
|
|
|
13135
13573
|
// lib/core-principles.ts
|
|
13136
|
-
function
|
|
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
|
|
13162
|
-
const links =
|
|
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 =
|
|
13171
|
-
const subPrinciples =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
14005
|
+
const repository = buildReadOnlyArtifactsRepository(fileSystem, dustPath);
|
|
14006
|
+
const localHierarchy = await repository.getRepositoryPrincipleHierarchy();
|
|
13603
14007
|
stdout(`${colors.bold}Local${colors.reset}`);
|
|
13604
|
-
|
|
14008
|
+
renderRepositoryPrincipleHierarchy(localHierarchy, (line) => stdout(line));
|
|
13605
14009
|
stdout("");
|
|
13606
14010
|
const collectedItems = [];
|
|
13607
14011
|
for (const file of localMdFiles) {
|