@joshski/dust 0.1.77 → 0.1.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifacts/index.d.ts +15 -3
- package/dist/artifacts.js +42 -0
- package/dist/cli/colors.d.ts +1 -1
- package/dist/cli/commands/focus.d.ts +1 -1
- package/dist/config/settings.d.ts +7 -8
- package/dist/dust.js +229 -173
- package/dist/types.d.ts +2 -0
- package/dist/validation.js +10 -2
- package/package.json +1 -1
|
@@ -3,8 +3,19 @@ import { type Fact } from './facts';
|
|
|
3
3
|
import { type Idea, type IdeaOpenQuestion, type IdeaOption, parseOpenQuestions } from './ideas';
|
|
4
4
|
import { type Principle } from './principles';
|
|
5
5
|
import { type Task } from './tasks';
|
|
6
|
-
import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch } from './workflow-tasks';
|
|
7
|
-
export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, };
|
|
6
|
+
import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, type WorkflowTaskMatch, type WorkflowTaskType } from './workflow-tasks';
|
|
7
|
+
export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
|
|
8
|
+
export interface TaskGraphNode {
|
|
9
|
+
task: Task;
|
|
10
|
+
workflowType: WorkflowTaskType | null;
|
|
11
|
+
}
|
|
12
|
+
export interface TaskGraph {
|
|
13
|
+
nodes: TaskGraphNode[];
|
|
14
|
+
edges: Array<{
|
|
15
|
+
from: string;
|
|
16
|
+
to: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
8
19
|
export { CAPTURE_IDEA_PREFIX, findAllWorkflowTasks, parseOpenQuestions };
|
|
9
20
|
export type { IdeaInProgress };
|
|
10
21
|
export interface ArtifactsRepository {
|
|
@@ -49,6 +60,7 @@ export interface ArtifactsRepository {
|
|
|
49
60
|
parseCaptureIdeaTask(options: {
|
|
50
61
|
taskSlug: string;
|
|
51
62
|
}): Promise<ParsedCaptureIdeaTask | null>;
|
|
63
|
+
buildTaskGraph(): Promise<TaskGraph>;
|
|
52
64
|
}
|
|
53
65
|
export declare function buildArtifactsRepository(fileSystem: FileSystem, dustPath: string): ArtifactsRepository;
|
|
54
|
-
export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask'>;
|
|
66
|
+
export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask' | 'buildTaskGraph'>;
|
package/dist/artifacts.js
CHANGED
|
@@ -609,6 +609,27 @@ function buildArtifactsRepository(fileSystem, dustPath) {
|
|
|
609
609
|
},
|
|
610
610
|
async parseCaptureIdeaTask(options) {
|
|
611
611
|
return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
|
|
612
|
+
},
|
|
613
|
+
async buildTaskGraph() {
|
|
614
|
+
const taskSlugs = await this.listTasks();
|
|
615
|
+
const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
|
|
616
|
+
const workflowTypeByTaskSlug = new Map;
|
|
617
|
+
for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
|
|
618
|
+
workflowTypeByTaskSlug.set(match.taskSlug, match.type);
|
|
619
|
+
}
|
|
620
|
+
const nodes = [];
|
|
621
|
+
const edges = [];
|
|
622
|
+
for (const slug of taskSlugs) {
|
|
623
|
+
const task = await this.parseTask({ slug });
|
|
624
|
+
nodes.push({
|
|
625
|
+
task,
|
|
626
|
+
workflowType: workflowTypeByTaskSlug.get(slug) ?? null
|
|
627
|
+
});
|
|
628
|
+
for (const blockerSlug of task.blockedBy) {
|
|
629
|
+
edges.push({ from: blockerSlug, to: slug });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return { nodes, edges };
|
|
612
633
|
}
|
|
613
634
|
};
|
|
614
635
|
}
|
|
@@ -663,6 +684,27 @@ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
|
|
|
663
684
|
},
|
|
664
685
|
async parseCaptureIdeaTask(options) {
|
|
665
686
|
return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
|
|
687
|
+
},
|
|
688
|
+
async buildTaskGraph() {
|
|
689
|
+
const taskSlugs = await this.listTasks();
|
|
690
|
+
const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
|
|
691
|
+
const workflowTypeByTaskSlug = new Map;
|
|
692
|
+
for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
|
|
693
|
+
workflowTypeByTaskSlug.set(match.taskSlug, match.type);
|
|
694
|
+
}
|
|
695
|
+
const nodes = [];
|
|
696
|
+
const edges = [];
|
|
697
|
+
for (const slug of taskSlugs) {
|
|
698
|
+
const task = await this.parseTask({ slug });
|
|
699
|
+
nodes.push({
|
|
700
|
+
task,
|
|
701
|
+
workflowType: workflowTypeByTaskSlug.get(slug) ?? null
|
|
702
|
+
});
|
|
703
|
+
for (const blockerSlug of task.blockedBy) {
|
|
704
|
+
edges.push({ from: blockerSlug, to: slug });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return { nodes, edges };
|
|
666
708
|
}
|
|
667
709
|
};
|
|
668
710
|
}
|
package/dist/cli/colors.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface Colors {
|
|
|
17
17
|
/**
|
|
18
18
|
* Determines whether colors should be disabled based on environment.
|
|
19
19
|
*/
|
|
20
|
-
export declare function shouldDisableColors(): boolean;
|
|
20
|
+
export declare function shouldDisableColors(env?: NodeJS.ProcessEnv): boolean;
|
|
21
21
|
/**
|
|
22
22
|
* Returns the appropriate colors object based on environment detection.
|
|
23
23
|
*/
|
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
* Usage: dust focus "add login box"
|
|
8
8
|
*/
|
|
9
9
|
import type { CommandDependencies, CommandResult } from '../types';
|
|
10
|
-
export declare function buildImplementationInstructions(bin: string, hooksInstalled: boolean, taskTitle?: string): string;
|
|
10
|
+
export declare function buildImplementationInstructions(bin: string, hooksInstalled: boolean, taskTitle?: string, taskPath?: string, installCommand?: string): string;
|
|
11
11
|
export declare function focus(dependencies: CommandDependencies): Promise<CommandResult>;
|
|
@@ -20,15 +20,14 @@ export declare function validateSettingsJson(content: string): SettingsViolation
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function detectDustCommand(cwd: string, fileSystem: ReadableFileSystem): string;
|
|
22
22
|
/**
|
|
23
|
-
* Detects the appropriate install command based on lockfiles
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* 5. Default → npm install
|
|
23
|
+
* Detects the appropriate install command based on lockfiles.
|
|
24
|
+
* Returns null when:
|
|
25
|
+
* - No recognized lockfile is found
|
|
26
|
+
* - Multiple ecosystems are detected (requires explicit configuration)
|
|
27
|
+
*
|
|
28
|
+
* Priority within each ecosystem follows the order in LOCKFILE_COMMANDS.
|
|
30
29
|
*/
|
|
31
|
-
export declare function detectInstallCommand(cwd: string, fileSystem: ReadableFileSystem): string;
|
|
30
|
+
export declare function detectInstallCommand(cwd: string, fileSystem: ReadableFileSystem): string | null;
|
|
32
31
|
/**
|
|
33
32
|
* Detects the appropriate test command based on lockfiles and environment.
|
|
34
33
|
* Priority:
|
package/dist/dust.js
CHANGED
|
@@ -170,8 +170,7 @@ function validateSettingsJson(content) {
|
|
|
170
170
|
return violations;
|
|
171
171
|
}
|
|
172
172
|
var DEFAULT_SETTINGS = {
|
|
173
|
-
dustCommand: "npx dust"
|
|
174
|
-
installCommand: "npm install"
|
|
173
|
+
dustCommand: "npx dust"
|
|
175
174
|
};
|
|
176
175
|
function detectDustCommand(cwd, fileSystem) {
|
|
177
176
|
if (fileSystem.exists(join(cwd, "bun.lockb"))) {
|
|
@@ -188,20 +187,38 @@ function detectDustCommand(cwd, fileSystem) {
|
|
|
188
187
|
}
|
|
189
188
|
return "npx dust";
|
|
190
189
|
}
|
|
190
|
+
var LOCKFILE_COMMANDS = [
|
|
191
|
+
{ file: "bun.lockb", command: "bun install", ecosystem: "js" },
|
|
192
|
+
{ file: "pnpm-lock.yaml", command: "pnpm install", ecosystem: "js" },
|
|
193
|
+
{ file: "package-lock.json", command: "npm install", ecosystem: "js" },
|
|
194
|
+
{ file: "Gemfile.lock", command: "bundle install", ecosystem: "ruby" },
|
|
195
|
+
{ file: "poetry.lock", command: "poetry install", ecosystem: "python" },
|
|
196
|
+
{ file: "Pipfile.lock", command: "pipenv install", ecosystem: "python" },
|
|
197
|
+
{
|
|
198
|
+
file: "requirements.txt",
|
|
199
|
+
command: "pip install -r requirements.txt",
|
|
200
|
+
ecosystem: "python"
|
|
201
|
+
},
|
|
202
|
+
{ file: "go.sum", command: "go mod download", ecosystem: "go" },
|
|
203
|
+
{ file: "Cargo.lock", command: "cargo build", ecosystem: "rust" },
|
|
204
|
+
{ file: "composer.lock", command: "composer install", ecosystem: "php" },
|
|
205
|
+
{ file: "mix.lock", command: "mix deps.get", ecosystem: "elixir" }
|
|
206
|
+
];
|
|
191
207
|
function detectInstallCommand(cwd, fileSystem) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
const foundEcosystems = new Set;
|
|
209
|
+
let firstCommand = null;
|
|
210
|
+
for (const { file, command, ecosystem } of LOCKFILE_COMMANDS) {
|
|
211
|
+
if (fileSystem.exists(join(cwd, file))) {
|
|
212
|
+
if (firstCommand === null) {
|
|
213
|
+
firstCommand = command;
|
|
214
|
+
}
|
|
215
|
+
foundEcosystems.add(ecosystem);
|
|
216
|
+
}
|
|
200
217
|
}
|
|
201
|
-
if (
|
|
202
|
-
return
|
|
218
|
+
if (foundEcosystems.size > 1) {
|
|
219
|
+
return null;
|
|
203
220
|
}
|
|
204
|
-
return
|
|
221
|
+
return firstCommand;
|
|
205
222
|
}
|
|
206
223
|
function detectTestCommand(cwd, fileSystem) {
|
|
207
224
|
if (fileSystem.exists(join(cwd, "bun.lockb")) || fileSystem.exists(join(cwd, "bun.lock"))) {
|
|
@@ -234,9 +251,12 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
234
251
|
const settingsPath = join(cwd, ".dust", "config", "settings.json");
|
|
235
252
|
if (!fileSystem.exists(settingsPath)) {
|
|
236
253
|
const result = {
|
|
237
|
-
dustCommand: detectDustCommand(cwd, fileSystem)
|
|
238
|
-
installCommand: detectInstallCommand(cwd, fileSystem)
|
|
254
|
+
dustCommand: detectDustCommand(cwd, fileSystem)
|
|
239
255
|
};
|
|
256
|
+
const installCommand = detectInstallCommand(cwd, fileSystem);
|
|
257
|
+
if (installCommand !== null) {
|
|
258
|
+
result.installCommand = installCommand;
|
|
259
|
+
}
|
|
240
260
|
if (process.env.DUST_EVENTS_URL) {
|
|
241
261
|
result.eventsUrl = process.env.DUST_EVENTS_URL;
|
|
242
262
|
}
|
|
@@ -256,26 +276,37 @@ async function loadSettings(cwd, fileSystem) {
|
|
|
256
276
|
result.dustCommand = detectDustCommand(cwd, fileSystem);
|
|
257
277
|
}
|
|
258
278
|
if (!parsed.installCommand) {
|
|
259
|
-
|
|
279
|
+
const installCommand = detectInstallCommand(cwd, fileSystem);
|
|
280
|
+
if (installCommand !== null) {
|
|
281
|
+
result.installCommand = installCommand;
|
|
282
|
+
} else {
|
|
283
|
+
delete result.installCommand;
|
|
284
|
+
}
|
|
260
285
|
}
|
|
261
286
|
if (process.env.DUST_EVENTS_URL) {
|
|
262
287
|
result.eventsUrl = process.env.DUST_EVENTS_URL;
|
|
263
288
|
}
|
|
264
289
|
return result;
|
|
265
|
-
} catch {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error.code === "ENOENT") {
|
|
292
|
+
const result = {
|
|
293
|
+
dustCommand: detectDustCommand(cwd, fileSystem)
|
|
294
|
+
};
|
|
295
|
+
const installCommand = detectInstallCommand(cwd, fileSystem);
|
|
296
|
+
if (installCommand !== null) {
|
|
297
|
+
result.installCommand = installCommand;
|
|
298
|
+
}
|
|
299
|
+
if (process.env.DUST_EVENTS_URL) {
|
|
300
|
+
result.eventsUrl = process.env.DUST_EVENTS_URL;
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
272
303
|
}
|
|
273
|
-
|
|
304
|
+
throw error;
|
|
274
305
|
}
|
|
275
306
|
}
|
|
276
307
|
|
|
277
308
|
// lib/version.ts
|
|
278
|
-
var DUST_VERSION = "0.1.
|
|
309
|
+
var DUST_VERSION = "0.1.78";
|
|
279
310
|
|
|
280
311
|
// lib/session.ts
|
|
281
312
|
var DUST_UNATTENDED = "DUST_UNATTENDED";
|
|
@@ -460,8 +491,11 @@ async function loadAgentInstructions(cwd, fileSystem, agentType) {
|
|
|
460
491
|
try {
|
|
461
492
|
const content = await fileSystem.readFile(instructionsPath);
|
|
462
493
|
return content.trim();
|
|
463
|
-
} catch {
|
|
464
|
-
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (error.code === "ENOENT") {
|
|
496
|
+
return "";
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
465
499
|
}
|
|
466
500
|
}
|
|
467
501
|
function templateVariables(settings, hooksInstalled, env = process.env, options) {
|
|
@@ -1521,11 +1555,11 @@ var NO_COLORS = {
|
|
|
1521
1555
|
green: "",
|
|
1522
1556
|
yellow: ""
|
|
1523
1557
|
};
|
|
1524
|
-
function shouldDisableColors() {
|
|
1525
|
-
if (
|
|
1558
|
+
function shouldDisableColors(env = process.env) {
|
|
1559
|
+
if (env.NO_COLOR !== undefined) {
|
|
1526
1560
|
return true;
|
|
1527
1561
|
}
|
|
1528
|
-
if (
|
|
1562
|
+
if (env.TERM === "dumb") {
|
|
1529
1563
|
return true;
|
|
1530
1564
|
}
|
|
1531
1565
|
if (!process.stdout.isTTY) {
|
|
@@ -1641,8 +1675,8 @@ var CREDENTIALS_DIR = ".dust";
|
|
|
1641
1675
|
var CREDENTIALS_FILE = "credentials.json";
|
|
1642
1676
|
var AUTH_TIMEOUT_MS = 120000;
|
|
1643
1677
|
var DEFAULT_DUSTBUCKET_HOST = "https://dustbucket.com";
|
|
1644
|
-
function getDustbucketHost() {
|
|
1645
|
-
return
|
|
1678
|
+
function getDustbucketHost(env = process.env) {
|
|
1679
|
+
return env.DUST_BUCKET_HOST || DEFAULT_DUSTBUCKET_HOST;
|
|
1646
1680
|
}
|
|
1647
1681
|
function credentialsPath(homeDir) {
|
|
1648
1682
|
return join4(homeDir, CREDENTIALS_DIR, CREDENTIALS_FILE);
|
|
@@ -1653,8 +1687,11 @@ async function loadStoredToken(fileSystem, homeDir) {
|
|
|
1653
1687
|
const content = await fileSystem.readFile(path);
|
|
1654
1688
|
const data = JSON.parse(content);
|
|
1655
1689
|
return typeof data.token === "string" ? data.token : null;
|
|
1656
|
-
} catch {
|
|
1657
|
-
|
|
1690
|
+
} catch (error) {
|
|
1691
|
+
if (error.code === "ENOENT") {
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
throw error;
|
|
1658
1695
|
}
|
|
1659
1696
|
}
|
|
1660
1697
|
async function storeToken(fileSystem, homeDir, token) {
|
|
@@ -1666,7 +1703,12 @@ async function clearToken(fileSystem, homeDir) {
|
|
|
1666
1703
|
const path = credentialsPath(homeDir);
|
|
1667
1704
|
try {
|
|
1668
1705
|
await fileSystem.writeFile(path, "{}");
|
|
1669
|
-
} catch {
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
if (error.code === "ENOENT") {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
throw error;
|
|
1711
|
+
}
|
|
1670
1712
|
}
|
|
1671
1713
|
async function defaultExchangeCode(code) {
|
|
1672
1714
|
const host = getDustbucketHost();
|
|
@@ -1847,6 +1889,134 @@ import { dirname as dirname2 } from "node:path";
|
|
|
1847
1889
|
// lib/claude/spawn-claude-code.ts
|
|
1848
1890
|
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
1849
1891
|
import { createInterface as nodeCreateInterface } from "node:readline";
|
|
1892
|
+
|
|
1893
|
+
// lib/logging/index.ts
|
|
1894
|
+
import { join as join6 } from "node:path";
|
|
1895
|
+
|
|
1896
|
+
// lib/logging/match.ts
|
|
1897
|
+
function parsePatterns(debug) {
|
|
1898
|
+
if (!debug)
|
|
1899
|
+
return [];
|
|
1900
|
+
const expressions = debug.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1901
|
+
return expressions.map((expr) => {
|
|
1902
|
+
const escaped = expr.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
1903
|
+
const pattern = escaped.replace(/\*/g, ".*");
|
|
1904
|
+
return new RegExp(`^${pattern}$`);
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
function matchesAny(name, patterns) {
|
|
1908
|
+
return patterns.some((re) => re.test(name));
|
|
1909
|
+
}
|
|
1910
|
+
function formatLine(name, messages) {
|
|
1911
|
+
const text = messages.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
1912
|
+
return `${new Date().toISOString()} [${name}] ${text}
|
|
1913
|
+
`;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// lib/logging/sink.ts
|
|
1917
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
1918
|
+
import { dirname } from "node:path";
|
|
1919
|
+
|
|
1920
|
+
class FileSink {
|
|
1921
|
+
logPath;
|
|
1922
|
+
_appendFileSync;
|
|
1923
|
+
_mkdirSync;
|
|
1924
|
+
resolvedPath;
|
|
1925
|
+
ready = false;
|
|
1926
|
+
constructor(logPath, _appendFileSync = appendFileSync, _mkdirSync = mkdirSync) {
|
|
1927
|
+
this.logPath = logPath;
|
|
1928
|
+
this._appendFileSync = _appendFileSync;
|
|
1929
|
+
this._mkdirSync = _mkdirSync;
|
|
1930
|
+
}
|
|
1931
|
+
ensureLogFile() {
|
|
1932
|
+
if (this.ready)
|
|
1933
|
+
return this.resolvedPath;
|
|
1934
|
+
this.ready = true;
|
|
1935
|
+
this.resolvedPath = this.logPath;
|
|
1936
|
+
try {
|
|
1937
|
+
this._mkdirSync(dirname(this.logPath), { recursive: true });
|
|
1938
|
+
} catch {
|
|
1939
|
+
this.resolvedPath = undefined;
|
|
1940
|
+
}
|
|
1941
|
+
return this.resolvedPath;
|
|
1942
|
+
}
|
|
1943
|
+
write(line) {
|
|
1944
|
+
const path = this.ensureLogFile();
|
|
1945
|
+
if (!path)
|
|
1946
|
+
return;
|
|
1947
|
+
try {
|
|
1948
|
+
this._appendFileSync(path, line);
|
|
1949
|
+
} catch {}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// lib/logging/index.ts
|
|
1954
|
+
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
1955
|
+
function createLoggingService() {
|
|
1956
|
+
let patterns = null;
|
|
1957
|
+
let initialized = false;
|
|
1958
|
+
let activeFileSink = null;
|
|
1959
|
+
const fileSinkCache = new Map;
|
|
1960
|
+
function init() {
|
|
1961
|
+
if (initialized)
|
|
1962
|
+
return;
|
|
1963
|
+
initialized = true;
|
|
1964
|
+
const parsed = parsePatterns(process.env.DEBUG);
|
|
1965
|
+
patterns = parsed.length > 0 ? parsed : null;
|
|
1966
|
+
}
|
|
1967
|
+
function getOrCreateFileSink(path) {
|
|
1968
|
+
let sink = fileSinkCache.get(path);
|
|
1969
|
+
if (!sink) {
|
|
1970
|
+
sink = new FileSink(path);
|
|
1971
|
+
fileSinkCache.set(path, sink);
|
|
1972
|
+
}
|
|
1973
|
+
return sink;
|
|
1974
|
+
}
|
|
1975
|
+
return {
|
|
1976
|
+
enableFileLogs(scope, sinkForTesting) {
|
|
1977
|
+
const existing = process.env[DUST_LOG_FILE];
|
|
1978
|
+
const logDir = process.env.DUST_LOG_DIR ?? join6(process.cwd(), "log");
|
|
1979
|
+
const path = existing ?? join6(logDir, `${scope}.log`);
|
|
1980
|
+
if (!existing) {
|
|
1981
|
+
process.env[DUST_LOG_FILE] = path;
|
|
1982
|
+
}
|
|
1983
|
+
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
1984
|
+
},
|
|
1985
|
+
createLogger(name, options) {
|
|
1986
|
+
let perLoggerSink;
|
|
1987
|
+
if (options?.file === false) {
|
|
1988
|
+
perLoggerSink = null;
|
|
1989
|
+
} else if (typeof options?.file === "string") {
|
|
1990
|
+
perLoggerSink = getOrCreateFileSink(options.file);
|
|
1991
|
+
}
|
|
1992
|
+
return (...messages) => {
|
|
1993
|
+
init();
|
|
1994
|
+
const line = formatLine(name, messages);
|
|
1995
|
+
if (perLoggerSink !== undefined) {
|
|
1996
|
+
if (perLoggerSink !== null) {
|
|
1997
|
+
perLoggerSink.write(line);
|
|
1998
|
+
}
|
|
1999
|
+
} else if (activeFileSink) {
|
|
2000
|
+
activeFileSink.write(line);
|
|
2001
|
+
}
|
|
2002
|
+
if (patterns && matchesAny(name, patterns)) {
|
|
2003
|
+
process.stdout.write(line);
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
},
|
|
2007
|
+
isEnabled(name) {
|
|
2008
|
+
init();
|
|
2009
|
+
return patterns !== null && matchesAny(name, patterns);
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
var defaultService = createLoggingService();
|
|
2014
|
+
var enableFileLogs = defaultService.enableFileLogs.bind(defaultService);
|
|
2015
|
+
var createLogger = defaultService.createLogger.bind(defaultService);
|
|
2016
|
+
var isEnabled = defaultService.isEnabled.bind(defaultService);
|
|
2017
|
+
|
|
2018
|
+
// lib/claude/spawn-claude-code.ts
|
|
2019
|
+
var debug = createLogger("dust.claude.spawn-claude-code");
|
|
1850
2020
|
var defaultDependencies = {
|
|
1851
2021
|
spawn: nodeSpawn2,
|
|
1852
2022
|
createInterface: nodeCreateInterface
|
|
@@ -1929,7 +2099,9 @@ async function* spawnClaudeCode(prompt, options = {}, dependencies = defaultDepe
|
|
|
1929
2099
|
continue;
|
|
1930
2100
|
try {
|
|
1931
2101
|
yield JSON.parse(line);
|
|
1932
|
-
} catch {
|
|
2102
|
+
} catch {
|
|
2103
|
+
debug("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
2104
|
+
}
|
|
1933
2105
|
}
|
|
1934
2106
|
await closePromise;
|
|
1935
2107
|
} finally {
|
|
@@ -2260,131 +2432,6 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
|
|
|
2260
2432
|
await dependencies.streamEvents(events, sink, onRawEvent);
|
|
2261
2433
|
}
|
|
2262
2434
|
|
|
2263
|
-
// lib/logging/index.ts
|
|
2264
|
-
import { join as join6 } from "node:path";
|
|
2265
|
-
|
|
2266
|
-
// lib/logging/match.ts
|
|
2267
|
-
function parsePatterns(debug) {
|
|
2268
|
-
if (!debug)
|
|
2269
|
-
return [];
|
|
2270
|
-
const expressions = debug.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2271
|
-
return expressions.map((expr) => {
|
|
2272
|
-
const escaped = expr.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
2273
|
-
const pattern = escaped.replace(/\*/g, ".*");
|
|
2274
|
-
return new RegExp(`^${pattern}$`);
|
|
2275
|
-
});
|
|
2276
|
-
}
|
|
2277
|
-
function matchesAny(name, patterns) {
|
|
2278
|
-
return patterns.some((re) => re.test(name));
|
|
2279
|
-
}
|
|
2280
|
-
function formatLine(name, messages) {
|
|
2281
|
-
const text = messages.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2282
|
-
return `${new Date().toISOString()} [${name}] ${text}
|
|
2283
|
-
`;
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2286
|
-
// lib/logging/sink.ts
|
|
2287
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2288
|
-
import { dirname } from "node:path";
|
|
2289
|
-
|
|
2290
|
-
class FileSink {
|
|
2291
|
-
logPath;
|
|
2292
|
-
_appendFileSync;
|
|
2293
|
-
_mkdirSync;
|
|
2294
|
-
resolvedPath;
|
|
2295
|
-
ready = false;
|
|
2296
|
-
constructor(logPath, _appendFileSync = appendFileSync, _mkdirSync = mkdirSync) {
|
|
2297
|
-
this.logPath = logPath;
|
|
2298
|
-
this._appendFileSync = _appendFileSync;
|
|
2299
|
-
this._mkdirSync = _mkdirSync;
|
|
2300
|
-
}
|
|
2301
|
-
ensureLogFile() {
|
|
2302
|
-
if (this.ready)
|
|
2303
|
-
return this.resolvedPath;
|
|
2304
|
-
this.ready = true;
|
|
2305
|
-
this.resolvedPath = this.logPath;
|
|
2306
|
-
try {
|
|
2307
|
-
this._mkdirSync(dirname(this.logPath), { recursive: true });
|
|
2308
|
-
} catch {
|
|
2309
|
-
this.resolvedPath = undefined;
|
|
2310
|
-
}
|
|
2311
|
-
return this.resolvedPath;
|
|
2312
|
-
}
|
|
2313
|
-
write(line) {
|
|
2314
|
-
const path = this.ensureLogFile();
|
|
2315
|
-
if (!path)
|
|
2316
|
-
return;
|
|
2317
|
-
try {
|
|
2318
|
-
this._appendFileSync(path, line);
|
|
2319
|
-
} catch {}
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
// lib/logging/index.ts
|
|
2324
|
-
var DUST_LOG_FILE = "DUST_LOG_FILE";
|
|
2325
|
-
function createLoggingService() {
|
|
2326
|
-
let patterns = null;
|
|
2327
|
-
let initialized = false;
|
|
2328
|
-
let activeFileSink = null;
|
|
2329
|
-
const fileSinkCache = new Map;
|
|
2330
|
-
function init() {
|
|
2331
|
-
if (initialized)
|
|
2332
|
-
return;
|
|
2333
|
-
initialized = true;
|
|
2334
|
-
const parsed = parsePatterns(process.env.DEBUG);
|
|
2335
|
-
patterns = parsed.length > 0 ? parsed : null;
|
|
2336
|
-
}
|
|
2337
|
-
function getOrCreateFileSink(path) {
|
|
2338
|
-
let sink = fileSinkCache.get(path);
|
|
2339
|
-
if (!sink) {
|
|
2340
|
-
sink = new FileSink(path);
|
|
2341
|
-
fileSinkCache.set(path, sink);
|
|
2342
|
-
}
|
|
2343
|
-
return sink;
|
|
2344
|
-
}
|
|
2345
|
-
return {
|
|
2346
|
-
enableFileLogs(scope, sinkForTesting) {
|
|
2347
|
-
const existing = process.env[DUST_LOG_FILE];
|
|
2348
|
-
const logDir = process.env.DUST_LOG_DIR ?? join6(process.cwd(), "log");
|
|
2349
|
-
const path = existing ?? join6(logDir, `${scope}.log`);
|
|
2350
|
-
if (!existing) {
|
|
2351
|
-
process.env[DUST_LOG_FILE] = path;
|
|
2352
|
-
}
|
|
2353
|
-
activeFileSink = sinkForTesting ?? new FileSink(path);
|
|
2354
|
-
},
|
|
2355
|
-
createLogger(name, options) {
|
|
2356
|
-
let perLoggerSink;
|
|
2357
|
-
if (options?.file === false) {
|
|
2358
|
-
perLoggerSink = null;
|
|
2359
|
-
} else if (typeof options?.file === "string") {
|
|
2360
|
-
perLoggerSink = getOrCreateFileSink(options.file);
|
|
2361
|
-
}
|
|
2362
|
-
return (...messages) => {
|
|
2363
|
-
init();
|
|
2364
|
-
const line = formatLine(name, messages);
|
|
2365
|
-
if (perLoggerSink !== undefined) {
|
|
2366
|
-
if (perLoggerSink !== null) {
|
|
2367
|
-
perLoggerSink.write(line);
|
|
2368
|
-
}
|
|
2369
|
-
} else if (activeFileSink) {
|
|
2370
|
-
activeFileSink.write(line);
|
|
2371
|
-
}
|
|
2372
|
-
if (patterns && matchesAny(name, patterns)) {
|
|
2373
|
-
process.stdout.write(line);
|
|
2374
|
-
}
|
|
2375
|
-
};
|
|
2376
|
-
},
|
|
2377
|
-
isEnabled(name) {
|
|
2378
|
-
init();
|
|
2379
|
-
return patterns !== null && matchesAny(name, patterns);
|
|
2380
|
-
}
|
|
2381
|
-
};
|
|
2382
|
-
}
|
|
2383
|
-
var defaultService = createLoggingService();
|
|
2384
|
-
var enableFileLogs = defaultService.enableFileLogs.bind(defaultService);
|
|
2385
|
-
var createLogger = defaultService.createLogger.bind(defaultService);
|
|
2386
|
-
var isEnabled = defaultService.isEnabled.bind(defaultService);
|
|
2387
|
-
|
|
2388
2435
|
// lib/bucket/repository-git.ts
|
|
2389
2436
|
import { join as join7 } from "node:path";
|
|
2390
2437
|
function getRepoPath(repoName, reposDir) {
|
|
@@ -2472,11 +2519,15 @@ function titleToFilename(title) {
|
|
|
2472
2519
|
}
|
|
2473
2520
|
|
|
2474
2521
|
// lib/cli/commands/focus.ts
|
|
2475
|
-
function buildImplementationInstructions(bin, hooksInstalled, taskTitle) {
|
|
2522
|
+
function buildImplementationInstructions(bin, hooksInstalled, taskTitle, taskPath, installCommand) {
|
|
2476
2523
|
const steps = [];
|
|
2477
2524
|
let step = 1;
|
|
2478
2525
|
const hasIdeaFile = !taskTitle?.startsWith(EXPEDITE_IDEA_PREFIX);
|
|
2479
2526
|
steps.push(`Note: Do NOT run \`${bin} agent\`.`, "");
|
|
2527
|
+
if (installCommand) {
|
|
2528
|
+
steps.push(`${step}. Run \`${installCommand}\` to install dependencies`);
|
|
2529
|
+
step++;
|
|
2530
|
+
}
|
|
2480
2531
|
steps.push(`${step}. Run \`${bin} check\` to verify the project is in a good state`);
|
|
2481
2532
|
step++;
|
|
2482
2533
|
steps.push(`${step}. Implement the task`);
|
|
@@ -2486,10 +2537,11 @@ function buildImplementationInstructions(bin, hooksInstalled, taskTitle) {
|
|
|
2486
2537
|
step++;
|
|
2487
2538
|
}
|
|
2488
2539
|
const commitMessageLine = taskTitle ? ` Use this exact commit message: "${taskTitle}". Do not add any prefix.` : ' Use the task title as the commit message. Do not add prefixes like "Complete task:" - use the title directly.';
|
|
2540
|
+
const deleteTaskLine = taskPath ? ` - Deletion of the completed task file (\`${taskPath}\`)` : " - Deletion of the completed task file";
|
|
2489
2541
|
const commitItems = [
|
|
2490
2542
|
" - All implementation changes",
|
|
2491
|
-
|
|
2492
|
-
|
|
2543
|
+
deleteTaskLine,
|
|
2544
|
+
` - Updates to any facts that changed (run \`${bin} facts\` if needed)`
|
|
2493
2545
|
];
|
|
2494
2546
|
if (hasIdeaFile) {
|
|
2495
2547
|
commitItems.push(" - Deletion of the idea file that spawned this task (if remaining scope exists, create new ideas for it)");
|
|
@@ -2795,18 +2847,14 @@ Make sure the repository is in a clean state and synced with remote before finis
|
|
|
2795
2847
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
2796
2848
|
const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
|
|
2797
2849
|
const { dustCommand, installCommand = "npm install" } = dependencies.settings;
|
|
2798
|
-
const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined);
|
|
2799
|
-
const prompt = `
|
|
2800
|
-
|
|
2801
|
-
The following is the contents of the task file \`${task.path}\`:
|
|
2850
|
+
const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined, task.path, installCommand);
|
|
2851
|
+
const prompt = `Implement the task at \`${task.path}\`:
|
|
2802
2852
|
|
|
2803
2853
|
----------
|
|
2804
2854
|
${taskContent}
|
|
2805
2855
|
----------
|
|
2806
2856
|
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
## Instructions
|
|
2857
|
+
## How to implement the task
|
|
2810
2858
|
|
|
2811
2859
|
${instructions}`;
|
|
2812
2860
|
onAgentEvent?.({
|
|
@@ -4039,7 +4087,12 @@ async function shutdown(state, bucketDeps, context) {
|
|
|
4039
4087
|
repoState.wakeUp?.();
|
|
4040
4088
|
}
|
|
4041
4089
|
const loopPromises = Array.from(state.repositories.values()).map((rs) => rs.loopPromise).filter((p) => p !== null);
|
|
4042
|
-
await Promise.
|
|
4090
|
+
const results = await Promise.allSettled(loopPromises);
|
|
4091
|
+
for (const result of results) {
|
|
4092
|
+
if (result.status === "rejected") {
|
|
4093
|
+
context.stderr(`Repository loop failed: ${result.reason}`);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4043
4096
|
for (const repoState of state.repositories.values()) {
|
|
4044
4097
|
await removeRepository(repoState.path, bucketDeps.spawn, context);
|
|
4045
4098
|
}
|
|
@@ -5698,6 +5751,7 @@ async function list(dependencies) {
|
|
|
5698
5751
|
// lib/codex/spawn-codex.ts
|
|
5699
5752
|
import { spawn as nodeSpawn5 } from "node:child_process";
|
|
5700
5753
|
import { createInterface as nodeCreateInterface2 } from "node:readline";
|
|
5754
|
+
var debug2 = createLogger("dust.codex.spawn-codex");
|
|
5701
5755
|
var defaultDependencies2 = {
|
|
5702
5756
|
spawn: nodeSpawn5,
|
|
5703
5757
|
createInterface: nodeCreateInterface2
|
|
@@ -5747,7 +5801,9 @@ async function* spawnCodex(prompt, options = {}, dependencies = defaultDependenc
|
|
|
5747
5801
|
continue;
|
|
5748
5802
|
try {
|
|
5749
5803
|
yield JSON.parse(line);
|
|
5750
|
-
} catch {
|
|
5804
|
+
} catch {
|
|
5805
|
+
debug2("Skipping malformed JSON line: %s", line.slice(0, 200));
|
|
5806
|
+
}
|
|
5751
5807
|
}
|
|
5752
5808
|
await closePromise;
|
|
5753
5809
|
} finally {
|
package/dist/types.d.ts
CHANGED
|
@@ -6,5 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export type { AgentSessionEvent, EventMessage } from './agent-events';
|
|
8
8
|
export type { Idea, IdeaOpenQuestion, IdeaOption } from './artifacts/ideas';
|
|
9
|
+
export type { TaskGraph, TaskGraphNode } from './artifacts/index';
|
|
10
|
+
export type { Task } from './artifacts/tasks';
|
|
9
11
|
export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, WorkflowTaskType, } from './artifacts/workflow-tasks';
|
|
10
12
|
export type { Repository } from './bucket/repository';
|
package/dist/validation.js
CHANGED
|
@@ -730,7 +730,11 @@ function createOverlayFileSystem(base, patchFiles, deletedPaths = new Set) {
|
|
|
730
730
|
entries.add(entry);
|
|
731
731
|
}
|
|
732
732
|
}
|
|
733
|
-
} catch {
|
|
733
|
+
} catch (error) {
|
|
734
|
+
if (error.code !== "ENOENT") {
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
734
738
|
return Array.from(entries);
|
|
735
739
|
},
|
|
736
740
|
isDirectory(path) {
|
|
@@ -821,7 +825,11 @@ async function validatePatch(fileSystem, dustPath, patch) {
|
|
|
821
825
|
const content = await overlayFs.readFile(filePath);
|
|
822
826
|
allRelationships.push(extractPrincipleRelationships(filePath, content));
|
|
823
827
|
}
|
|
824
|
-
} catch {
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (error.code !== "ENOENT") {
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
825
833
|
violations.push(...validateBidirectionalLinks(allRelationships));
|
|
826
834
|
violations.push(...validateNoCycles(allRelationships));
|
|
827
835
|
}
|