@joshski/dust 0.1.109 → 0.1.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/audits.js +163 -0
- 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 +644 -75
- package/dist/patch.js +5 -4
- package/dist/validation.js +5 -4
- 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
|
}
|
package/dist/audits.js
CHANGED
|
@@ -1865,6 +1865,168 @@ function slowTests() {
|
|
|
1865
1865
|
- No changes to files outside \`.dust/\`
|
|
1866
1866
|
`;
|
|
1867
1867
|
}
|
|
1868
|
+
function overAbstraction() {
|
|
1869
|
+
return dedent`
|
|
1870
|
+
# Over-Abstraction
|
|
1871
|
+
|
|
1872
|
+
Identify violations of the "reasonably-dry" principle where code has been over-engineered with excessive abstraction.
|
|
1873
|
+
|
|
1874
|
+
${ideasHint}
|
|
1875
|
+
|
|
1876
|
+
## Scope
|
|
1877
|
+
|
|
1878
|
+
Detect these over-abstraction patterns:
|
|
1879
|
+
|
|
1880
|
+
1. **Single-use abstractions** - Interfaces, base classes, or utility functions used in only one place
|
|
1881
|
+
2. **Deep inheritance hierarchies** - Classes extending more than 2 levels deep
|
|
1882
|
+
3. **Premature generalization** - Parameters always used with the same value, unused options/flags
|
|
1883
|
+
4. **Excessive indirection** - Multiple layers of wrappers adding no value
|
|
1884
|
+
|
|
1885
|
+
## Analysis Steps
|
|
1886
|
+
|
|
1887
|
+
### 1. Find Single-Use Abstractions
|
|
1888
|
+
|
|
1889
|
+
Search for abstractions that are only used once:
|
|
1890
|
+
|
|
1891
|
+
1. **Interfaces with one implementation**
|
|
1892
|
+
- Search for \`interface\` declarations
|
|
1893
|
+
- Check if each interface has only one implementing class
|
|
1894
|
+
- Flag interfaces that exist solely for testing (can be replaced with the concrete type)
|
|
1895
|
+
|
|
1896
|
+
2. **Base classes with one subclass**
|
|
1897
|
+
- Search for \`abstract class\` or classes used as base classes
|
|
1898
|
+
- Count implementations extending each base class
|
|
1899
|
+
- Flag base classes with only one subclass
|
|
1900
|
+
|
|
1901
|
+
3. **Utility functions called once**
|
|
1902
|
+
- Search for exported utility functions
|
|
1903
|
+
- Check call sites - if only called from one location, it's over-abstraction
|
|
1904
|
+
- Consider inlining single-use utilities
|
|
1905
|
+
|
|
1906
|
+
4. **Generic types with one concrete usage**
|
|
1907
|
+
- Find generic type parameters: \`<T>\`, \`<TData>\`, etc.
|
|
1908
|
+
- Check if T is always the same type at all call sites
|
|
1909
|
+
- Flag generics that could be concrete types
|
|
1910
|
+
|
|
1911
|
+
### 2. Detect Deep Inheritance Hierarchies
|
|
1912
|
+
|
|
1913
|
+
Find inheritance chains longer than 2 levels:
|
|
1914
|
+
|
|
1915
|
+
1. Search for \`extends\` keywords in class declarations
|
|
1916
|
+
2. Build inheritance tree for each class
|
|
1917
|
+
3. Flag chains deeper than 2 (A extends B extends C extends D...)
|
|
1918
|
+
4. Respect framework conventions (don't flag React.Component, etc.)
|
|
1919
|
+
|
|
1920
|
+
### 3. Identify Premature Generalization
|
|
1921
|
+
|
|
1922
|
+
Look for flexibility that's never used:
|
|
1923
|
+
|
|
1924
|
+
1. **Always-same parameter values**
|
|
1925
|
+
- Find function parameters
|
|
1926
|
+
- Check all call sites - if always the same value, it's not needed
|
|
1927
|
+
- Flag parameters that could be constants or removed
|
|
1928
|
+
|
|
1929
|
+
2. **Unused configuration options**
|
|
1930
|
+
- Search for configuration objects/interfaces
|
|
1931
|
+
- Check which options are actually used
|
|
1932
|
+
- Flag options that are never set or always default
|
|
1933
|
+
|
|
1934
|
+
3. **Unused function parameters**
|
|
1935
|
+
- Find parameters that aren't referenced in function bodies
|
|
1936
|
+
- Flag as candidates for removal
|
|
1937
|
+
|
|
1938
|
+
### 4. Find Excessive Indirection
|
|
1939
|
+
|
|
1940
|
+
Detect wrapper chains that add no value:
|
|
1941
|
+
|
|
1942
|
+
1. **Delegation chains**
|
|
1943
|
+
- Search for functions that only call another function
|
|
1944
|
+
- Flag wrappers that don't add logic, just forward calls
|
|
1945
|
+
- Example: \`function foo(x) { return bar(x) }\`
|
|
1946
|
+
|
|
1947
|
+
2. **Proxy patterns without behavior**
|
|
1948
|
+
- Find classes that wrap another class
|
|
1949
|
+
- Check if wrapper adds any logic beyond forwarding
|
|
1950
|
+
- Flag pure proxies
|
|
1951
|
+
|
|
1952
|
+
3. **Middleware without transformation**
|
|
1953
|
+
- Look for middleware/interceptor patterns
|
|
1954
|
+
- Check if they modify data or just pass through
|
|
1955
|
+
- Flag pass-through middleware
|
|
1956
|
+
|
|
1957
|
+
## Output Format
|
|
1958
|
+
|
|
1959
|
+
For each over-abstraction found, create an idea file in \`.dust/ideas/\` with:
|
|
1960
|
+
|
|
1961
|
+
\`\`\`markdown
|
|
1962
|
+
# Over-Abstraction: [Type] in [Location]
|
|
1963
|
+
|
|
1964
|
+
## Type
|
|
1965
|
+
|
|
1966
|
+
[Single-use | Deep hierarchy | Premature generalization | Excessive indirection]
|
|
1967
|
+
|
|
1968
|
+
## Location
|
|
1969
|
+
|
|
1970
|
+
\`\`\`
|
|
1971
|
+
[file path]:[line number]
|
|
1972
|
+
\`\`\`
|
|
1973
|
+
|
|
1974
|
+
## Description
|
|
1975
|
+
|
|
1976
|
+
[What the abstraction is]
|
|
1977
|
+
|
|
1978
|
+
## Problem
|
|
1979
|
+
|
|
1980
|
+
[Why this is over-abstraction - complexity without benefit]
|
|
1981
|
+
|
|
1982
|
+
## Usage Analysis
|
|
1983
|
+
|
|
1984
|
+
- **Times used**: [count]
|
|
1985
|
+
- **Variation in usage**: [how different are the use cases]
|
|
1986
|
+
- **Complexity cost**: [lines of code, indirection levels, etc.]
|
|
1987
|
+
|
|
1988
|
+
## Suggested Simplification
|
|
1989
|
+
|
|
1990
|
+
[How to remove or reduce this abstraction]
|
|
1991
|
+
|
|
1992
|
+
## Impact
|
|
1993
|
+
|
|
1994
|
+
[Lines of code saved, reduced complexity, improved clarity]
|
|
1995
|
+
\`\`\`
|
|
1996
|
+
|
|
1997
|
+
## Special Considerations
|
|
1998
|
+
|
|
1999
|
+
1. **Framework conventions** - Don't flag patterns mandated by frameworks:
|
|
2000
|
+
- React: Component base classes, hooks patterns
|
|
2001
|
+
- Express: Middleware signatures
|
|
2002
|
+
- Testing: Test base classes, fixture patterns
|
|
2003
|
+
|
|
2004
|
+
2. **Library boundaries** - Public API abstractions may be justified even if internal usage is simple
|
|
2005
|
+
|
|
2006
|
+
3. **Test code** - Apply the same standards to test code as production code
|
|
2007
|
+
|
|
2008
|
+
4. **Context depth thresholds**:
|
|
2009
|
+
- Deep hierarchies (>2 levels) make understanding difficult
|
|
2010
|
+
- Wrapper chains (>2 levels) obscure actual behavior
|
|
2011
|
+
- Generic parameters should have multiple concrete usages
|
|
2012
|
+
|
|
2013
|
+
## Blocked By
|
|
2014
|
+
|
|
2015
|
+
(none)
|
|
2016
|
+
|
|
2017
|
+
## Definition of Done
|
|
2018
|
+
|
|
2019
|
+
- Searched for single-use interfaces, base classes, and utility functions
|
|
2020
|
+
- Identified deep inheritance hierarchies (>2 levels)
|
|
2021
|
+
- Found parameters always used with the same value
|
|
2022
|
+
- Detected unused configuration options
|
|
2023
|
+
- Located excessive wrapper chains and delegation
|
|
2024
|
+
- Respected framework conventions (didn't flag framework-mandated patterns)
|
|
2025
|
+
- Created idea files for each over-abstraction found
|
|
2026
|
+
- Each idea includes usage analysis and simplification suggestions
|
|
2027
|
+
- No changes to files outside \`.dust/\`
|
|
2028
|
+
`;
|
|
2029
|
+
}
|
|
1868
2030
|
function primitiveObsession() {
|
|
1869
2031
|
return dedent`
|
|
1870
2032
|
# Primitive Obsession
|
|
@@ -3240,6 +3402,7 @@ var stockAuditFunctions = {
|
|
|
3240
3402
|
"idiomatic-style": idiomaticStyle,
|
|
3241
3403
|
"incidental-test-details": incidentalTestDetails,
|
|
3242
3404
|
"logging-and-traceability": loggingAndTraceability,
|
|
3405
|
+
"over-abstraction": overAbstraction,
|
|
3243
3406
|
"primitive-obsession": primitiveObsession,
|
|
3244
3407
|
"repository-context": repositoryContext,
|
|
3245
3408
|
"security-review": securityReview,
|
|
@@ -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.
|