@joshski/dust 0.1.111 → 0.1.113
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 +17 -0
- package/dist/cli/shared/agent-shared.d.ts +9 -1
- package/dist/cli/types.d.ts +5 -1
- package/dist/core-principles.js +608 -608
- package/dist/dust.js +877 -681
- package/dist/execution-order.d.ts +17 -0
- package/dist/execution-order.js +39 -0
- package/dist/lint/validators/content-validator.d.ts +1 -0
- package/dist/loop/iteration.d.ts +4 -0
- package/dist/patch.js +33 -0
- package/dist/validation.js +33 -0
- package/package.json +5 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TaskNode {
|
|
2
|
+
slug: string;
|
|
3
|
+
blockedBy: string[];
|
|
4
|
+
lastCommittedAt: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface OrderedTask<T extends TaskNode> {
|
|
7
|
+
node: T;
|
|
8
|
+
executionOrder: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Computes execution order for tasks using topological sort.
|
|
12
|
+
* Dependencies always trump timestamps: a blocked task never appears
|
|
13
|
+
* before its blockers, regardless of commit time.
|
|
14
|
+
* Among unblocked peers, earlier lastCommittedAt values come first;
|
|
15
|
+
* null values sort last.
|
|
16
|
+
*/
|
|
17
|
+
export declare function computeExecutionOrder<T extends TaskNode>(nodes: T[]): OrderedTask<T>[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// lib/execution-order.ts
|
|
2
|
+
function computeExecutionOrder(nodes) {
|
|
3
|
+
if (nodes.length === 0)
|
|
4
|
+
return [];
|
|
5
|
+
const sorted = [...nodes].toSorted((a, b) => {
|
|
6
|
+
if (a.lastCommittedAt === null && b.lastCommittedAt === null)
|
|
7
|
+
return 0;
|
|
8
|
+
if (a.lastCommittedAt === null)
|
|
9
|
+
return 1;
|
|
10
|
+
if (b.lastCommittedAt === null)
|
|
11
|
+
return -1;
|
|
12
|
+
return new Date(a.lastCommittedAt).getTime() - new Date(b.lastCommittedAt).getTime();
|
|
13
|
+
});
|
|
14
|
+
const result = [];
|
|
15
|
+
const completed = new Set;
|
|
16
|
+
const nodeMap = new Map(nodes.map((n) => [n.slug, n]));
|
|
17
|
+
while (result.length < nodes.length) {
|
|
18
|
+
const next = sorted.find((node) => {
|
|
19
|
+
if (completed.has(node.slug))
|
|
20
|
+
return false;
|
|
21
|
+
return node.blockedBy.every((slug) => completed.has(slug) || !nodeMap.has(slug));
|
|
22
|
+
});
|
|
23
|
+
if (!next) {
|
|
24
|
+
for (const node of sorted) {
|
|
25
|
+
if (!completed.has(node.slug)) {
|
|
26
|
+
result.push({ node, executionOrder: result.length + 1 });
|
|
27
|
+
completed.add(node.slug);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
result.push({ node: next, executionOrder: result.length + 1 });
|
|
33
|
+
completed.add(next.slug);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
computeExecutionOrder
|
|
39
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ParsedArtifact } from '../../artifacts/parsed-artifact';
|
|
5
5
|
import type { Violation } from './types';
|
|
6
|
+
export declare function validateNoFrontMatter(artifact: ParsedArtifact): Violation | null;
|
|
6
7
|
export declare function validateOpeningSentence(artifact: ParsedArtifact): Violation | null;
|
|
7
8
|
export declare function validateOpeningSentenceLength(artifact: ParsedArtifact): Violation | null;
|
|
8
9
|
export declare function validateImperativeOpeningSentence(artifact: ParsedArtifact): Violation | null;
|
package/dist/loop/iteration.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
2
|
import type { BoundRunFn, DockerSpawnConfig } from '../claude/types';
|
|
3
3
|
import type { DockerDependencies } from '../docker/docker-agent';
|
|
4
|
+
import type { ContainerRuntime } from '../container/runtime';
|
|
4
5
|
import { type SessionConfig } from '../env-config';
|
|
5
6
|
import { type ShellRunner } from '../cli/process-runner';
|
|
6
7
|
import type { CommandDependencies } from '../cli/types';
|
|
@@ -40,11 +41,14 @@ export interface IterationOptions {
|
|
|
40
41
|
branch?: string;
|
|
41
42
|
/** Trace ID for correlating events across processes */
|
|
42
43
|
traceId?: string;
|
|
44
|
+
/** Container runtime to use for container-aware pre-flight checks */
|
|
45
|
+
containerRuntime?: ContainerRuntime | null;
|
|
43
46
|
}
|
|
44
47
|
export declare function findAvailableTasks(dependencies: CommandDependencies): Promise<{
|
|
45
48
|
tasks: UnblockedTask[];
|
|
46
49
|
invalidTasks: InvalidTask[];
|
|
47
50
|
}>;
|
|
51
|
+
export declare function buildContainerShellRunner(spawnFn: typeof nodeSpawn, containerRuntime: ContainerRuntime, docker: DockerSpawnConfig): ShellRunner;
|
|
48
52
|
export declare function runOneIteration(dependencies: CommandDependencies, loopDependencies: LoopDependencies, onLoopEvent: LoopEmitFn, onAgentEvent?: SendAgentEventFn, options?: IterationOptions): Promise<IterationResult>;
|
|
49
53
|
export declare function buildTaskPrompt(taskPath: string, taskContent: string, instructions: string, toolsSection: string, dustCommand: string, branch?: string): string;
|
|
50
54
|
export {};
|
package/dist/patch.js
CHANGED
|
@@ -251,6 +251,7 @@ function validateAuditHeadings(artifact) {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
// lib/lint/validators/content-validator.ts
|
|
254
|
+
var FRONT_MATTER_DELIMITER = "---";
|
|
254
255
|
var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
|
|
255
256
|
var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
|
|
256
257
|
var MAX_OPENING_SENTENCE_LENGTH = 150;
|
|
@@ -268,6 +269,18 @@ var NON_IMPERATIVE_STARTERS = new Set([
|
|
|
268
269
|
"you",
|
|
269
270
|
"i"
|
|
270
271
|
]);
|
|
272
|
+
function validateNoFrontMatter(artifact) {
|
|
273
|
+
const firstLine = artifact.rawContent.split(`
|
|
274
|
+
`)[0];
|
|
275
|
+
if (firstLine.trim() === FRONT_MATTER_DELIMITER) {
|
|
276
|
+
return {
|
|
277
|
+
file: artifact.filePath,
|
|
278
|
+
line: 1,
|
|
279
|
+
message: "Artifact must not contain front matter. The title must be the first line."
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
271
284
|
function validateOpeningSentence(artifact) {
|
|
272
285
|
if (!artifact.openingSentence) {
|
|
273
286
|
return {
|
|
@@ -453,6 +466,8 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
453
466
|
const topLevelStructureMessage = "Open Questions must use `### Question?` headings and `#### Option` headings at the top level. Put supporting markdown (including lists and code blocks) under an option heading. Run `dust new idea` to see the expected format.";
|
|
454
467
|
let inOpenQuestions = false;
|
|
455
468
|
let currentQuestionLine = null;
|
|
469
|
+
let currentQuestionText = null;
|
|
470
|
+
let currentQuestionOptionNames = new Set;
|
|
456
471
|
let inOption = false;
|
|
457
472
|
let inCodeBlock = false;
|
|
458
473
|
for (let i = 0;i < lines.length; i++) {
|
|
@@ -476,6 +491,8 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
476
491
|
violations.push(...validateH2Heading(filePath, line, i + 1, inOpenQuestions, currentQuestionLine));
|
|
477
492
|
inOpenQuestions = line === "## Open Questions";
|
|
478
493
|
currentQuestionLine = null;
|
|
494
|
+
currentQuestionText = null;
|
|
495
|
+
currentQuestionOptionNames = new Set;
|
|
479
496
|
inOption = false;
|
|
480
497
|
inCodeBlock = false;
|
|
481
498
|
continue;
|
|
@@ -491,6 +508,7 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
491
508
|
line: currentQuestionLine
|
|
492
509
|
});
|
|
493
510
|
}
|
|
511
|
+
currentQuestionOptionNames = new Set;
|
|
494
512
|
if (!trimmedLine.endsWith("?")) {
|
|
495
513
|
violations.push({
|
|
496
514
|
file: filePath,
|
|
@@ -498,12 +516,24 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
498
516
|
line: i + 1
|
|
499
517
|
});
|
|
500
518
|
currentQuestionLine = null;
|
|
519
|
+
currentQuestionText = null;
|
|
501
520
|
} else {
|
|
502
521
|
currentQuestionLine = i + 1;
|
|
522
|
+
currentQuestionText = trimmedLine.slice(4);
|
|
503
523
|
}
|
|
504
524
|
continue;
|
|
505
525
|
}
|
|
506
526
|
if (line.startsWith("#### ")) {
|
|
527
|
+
const optionName = trimmedLine.slice(5);
|
|
528
|
+
if (currentQuestionOptionNames.has(optionName)) {
|
|
529
|
+
violations.push({
|
|
530
|
+
file: filePath,
|
|
531
|
+
message: `Duplicate option "${optionName}" under question "${currentQuestionText}" — each option must have a unique name`,
|
|
532
|
+
line: i + 1
|
|
533
|
+
});
|
|
534
|
+
} else {
|
|
535
|
+
currentQuestionOptionNames.add(optionName);
|
|
536
|
+
}
|
|
507
537
|
currentQuestionLine = null;
|
|
508
538
|
inOption = true;
|
|
509
539
|
continue;
|
|
@@ -879,6 +909,9 @@ function validateArtifacts(context) {
|
|
|
879
909
|
}
|
|
880
910
|
for (const artifacts of Object.values(byType)) {
|
|
881
911
|
for (const artifact of artifacts) {
|
|
912
|
+
const frontMatterViolation = validateNoFrontMatter(artifact);
|
|
913
|
+
if (frontMatterViolation)
|
|
914
|
+
violations.push(frontMatterViolation);
|
|
882
915
|
const openingSentenceViolation = validateOpeningSentence(artifact);
|
|
883
916
|
if (openingSentenceViolation)
|
|
884
917
|
violations.push(openingSentenceViolation);
|
package/dist/validation.js
CHANGED
|
@@ -248,6 +248,7 @@ function validateAuditHeadings(artifact) {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
// lib/lint/validators/content-validator.ts
|
|
251
|
+
var FRONT_MATTER_DELIMITER = "---";
|
|
251
252
|
var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
|
|
252
253
|
var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
|
|
253
254
|
var MAX_OPENING_SENTENCE_LENGTH = 150;
|
|
@@ -265,6 +266,18 @@ var NON_IMPERATIVE_STARTERS = new Set([
|
|
|
265
266
|
"you",
|
|
266
267
|
"i"
|
|
267
268
|
]);
|
|
269
|
+
function validateNoFrontMatter(artifact) {
|
|
270
|
+
const firstLine = artifact.rawContent.split(`
|
|
271
|
+
`)[0];
|
|
272
|
+
if (firstLine.trim() === FRONT_MATTER_DELIMITER) {
|
|
273
|
+
return {
|
|
274
|
+
file: artifact.filePath,
|
|
275
|
+
line: 1,
|
|
276
|
+
message: "Artifact must not contain front matter. The title must be the first line."
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
268
281
|
function validateOpeningSentence(artifact) {
|
|
269
282
|
if (!artifact.openingSentence) {
|
|
270
283
|
return {
|
|
@@ -450,6 +463,8 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
450
463
|
const topLevelStructureMessage = "Open Questions must use `### Question?` headings and `#### Option` headings at the top level. Put supporting markdown (including lists and code blocks) under an option heading. Run `dust new idea` to see the expected format.";
|
|
451
464
|
let inOpenQuestions = false;
|
|
452
465
|
let currentQuestionLine = null;
|
|
466
|
+
let currentQuestionText = null;
|
|
467
|
+
let currentQuestionOptionNames = new Set;
|
|
453
468
|
let inOption = false;
|
|
454
469
|
let inCodeBlock = false;
|
|
455
470
|
for (let i = 0;i < lines.length; i++) {
|
|
@@ -473,6 +488,8 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
473
488
|
violations.push(...validateH2Heading(filePath, line, i + 1, inOpenQuestions, currentQuestionLine));
|
|
474
489
|
inOpenQuestions = line === "## Open Questions";
|
|
475
490
|
currentQuestionLine = null;
|
|
491
|
+
currentQuestionText = null;
|
|
492
|
+
currentQuestionOptionNames = new Set;
|
|
476
493
|
inOption = false;
|
|
477
494
|
inCodeBlock = false;
|
|
478
495
|
continue;
|
|
@@ -488,6 +505,7 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
488
505
|
line: currentQuestionLine
|
|
489
506
|
});
|
|
490
507
|
}
|
|
508
|
+
currentQuestionOptionNames = new Set;
|
|
491
509
|
if (!trimmedLine.endsWith("?")) {
|
|
492
510
|
violations.push({
|
|
493
511
|
file: filePath,
|
|
@@ -495,12 +513,24 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
495
513
|
line: i + 1
|
|
496
514
|
});
|
|
497
515
|
currentQuestionLine = null;
|
|
516
|
+
currentQuestionText = null;
|
|
498
517
|
} else {
|
|
499
518
|
currentQuestionLine = i + 1;
|
|
519
|
+
currentQuestionText = trimmedLine.slice(4);
|
|
500
520
|
}
|
|
501
521
|
continue;
|
|
502
522
|
}
|
|
503
523
|
if (line.startsWith("#### ")) {
|
|
524
|
+
const optionName = trimmedLine.slice(5);
|
|
525
|
+
if (currentQuestionOptionNames.has(optionName)) {
|
|
526
|
+
violations.push({
|
|
527
|
+
file: filePath,
|
|
528
|
+
message: `Duplicate option "${optionName}" under question "${currentQuestionText}" — each option must have a unique name`,
|
|
529
|
+
line: i + 1
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
currentQuestionOptionNames.add(optionName);
|
|
533
|
+
}
|
|
504
534
|
currentQuestionLine = null;
|
|
505
535
|
inOption = true;
|
|
506
536
|
continue;
|
|
@@ -876,6 +906,9 @@ function validateArtifacts(context) {
|
|
|
876
906
|
}
|
|
877
907
|
for (const artifacts of Object.values(byType)) {
|
|
878
908
|
for (const artifact of artifacts) {
|
|
909
|
+
const frontMatterViolation = validateNoFrontMatter(artifact);
|
|
910
|
+
if (frontMatterViolation)
|
|
911
|
+
violations.push(frontMatterViolation);
|
|
879
912
|
const openingSentenceViolation = validateOpeningSentence(artifact);
|
|
880
913
|
if (openingSentenceViolation)
|
|
881
914
|
violations.push(openingSentenceViolation);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joshski/dust",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.113",
|
|
4
4
|
"description": "Flow state for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"./core-principles": {
|
|
50
50
|
"import": "./dist/core-principles.js",
|
|
51
51
|
"types": "./dist/core-principles.d.ts"
|
|
52
|
+
},
|
|
53
|
+
"./execution-order": {
|
|
54
|
+
"import": "./dist/execution-order.js",
|
|
55
|
+
"types": "./dist/execution-order.d.ts"
|
|
52
56
|
}
|
|
53
57
|
},
|
|
54
58
|
"files": [
|