@joshski/dust 0.1.112 → 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/dust.js +177 -37
- 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 +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,23 @@ npx dust loop claude
|
|
|
40
40
|
|
|
41
41
|
This runs Claude Code in a [ralph loop](https://ghuntley.com/loop/), picking up tasks until they are all done.
|
|
42
42
|
|
|
43
|
+
## Codex Hook (Optional)
|
|
44
|
+
|
|
45
|
+
For [Codex](https://github.com/openai/codex) 0.125.0 or newer, you can replace the `AGENTS.md` instruction with a `SessionStart` hook that loads dust's instructions directly into the model's context — once per session, with no extra agent commands. Add this to `~/.codex/config.toml` (or your project's Codex config):
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
[features]
|
|
49
|
+
codex_hooks = true
|
|
50
|
+
|
|
51
|
+
[[hooks.SessionStart]]
|
|
52
|
+
matcher = "^startup$"
|
|
53
|
+
|
|
54
|
+
[[hooks.SessionStart.hooks]]
|
|
55
|
+
type = "command"
|
|
56
|
+
command = "bunx dust codex hook"
|
|
57
|
+
statusMessage = "Loading dust agent instructions"
|
|
58
|
+
```
|
|
59
|
+
|
|
43
60
|
## Learn More
|
|
44
61
|
|
|
45
62
|
Details live in the [.dust/facts](./.dust/facts) directory:
|
|
@@ -16,7 +16,7 @@ export interface TemplateVars {
|
|
|
16
16
|
isClaudeCodeWeb: boolean;
|
|
17
17
|
hasIdeaFile: boolean;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
interface TemplateVarsWithInstructions extends TemplateVars {
|
|
20
20
|
agentInstructions: string;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
@@ -33,6 +33,13 @@ export declare function templateVariables(settings: DustSettings, hooksInstalled
|
|
|
33
33
|
export declare function templateVariablesWithInstructions(cwd: string, fileSystem: FileReader, settings: DustSettings, hooksInstalled: boolean, env: NodeJS.ProcessEnv, options?: {
|
|
34
34
|
hasIdeaFile?: boolean;
|
|
35
35
|
}): Promise<TemplateVarsWithInstructions>;
|
|
36
|
+
/**
|
|
37
|
+
* Renders the dust agent greeting text.
|
|
38
|
+
*
|
|
39
|
+
* Used by both `dust agent` (printed to the user) and `dust codex hook`
|
|
40
|
+
* (injected as `additionalContext` into the model's session context).
|
|
41
|
+
*/
|
|
42
|
+
export declare function agentGreeting(vars: TemplateVarsWithInstructions): string;
|
|
36
43
|
/**
|
|
37
44
|
* Manages git hook installation for agent commands.
|
|
38
45
|
* Automatically installs pre-push hooks if:
|
|
@@ -42,3 +49,4 @@ export declare function templateVariablesWithInstructions(cwd: string, fileSyste
|
|
|
42
49
|
* Returns whether hooks are installed.
|
|
43
50
|
*/
|
|
44
51
|
export declare function manageGitHooks(dependencies: CommandDependencies): Promise<boolean>;
|
|
52
|
+
export {};
|
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.113",
|
|
11
11
|
description: "Flow state for AI coding agents",
|
|
12
12
|
type: "module",
|
|
13
13
|
bin: {
|
|
@@ -731,7 +731,7 @@ async function loadSettings(cwd, fileSystem, runtime) {
|
|
|
731
731
|
}
|
|
732
732
|
|
|
733
733
|
// lib/version.ts
|
|
734
|
-
var DUST_VERSION = "0.1.
|
|
734
|
+
var DUST_VERSION = "0.1.113";
|
|
735
735
|
|
|
736
736
|
// lib/cli/middleware.ts
|
|
737
737
|
function applyMiddleware(middlewares, execute) {
|
|
@@ -783,16 +783,6 @@ function createDefaultTracingOptions() {
|
|
|
783
783
|
};
|
|
784
784
|
}
|
|
785
785
|
|
|
786
|
-
// lib/cli/dedent.ts
|
|
787
|
-
function dedent(strings, ...values) {
|
|
788
|
-
const result = strings.reduce((acc, part, index) => acc + part + (values[index] ?? ""), "");
|
|
789
|
-
const lines = result.split(`
|
|
790
|
-
`);
|
|
791
|
-
const indent = lines.filter((line) => line.trim()).reduce((min, line) => Math.min(min, line.match(/^\s*/)[0].length), Number.POSITIVE_INFINITY);
|
|
792
|
-
return lines.map((line) => line.slice(indent)).join(`
|
|
793
|
-
`).trim();
|
|
794
|
-
}
|
|
795
|
-
|
|
796
786
|
// lib/cli/shared/agent-shared.ts
|
|
797
787
|
import { join as join4 } from "node:path";
|
|
798
788
|
|
|
@@ -939,6 +929,16 @@ ${newHookContent}
|
|
|
939
929
|
};
|
|
940
930
|
}
|
|
941
931
|
|
|
932
|
+
// lib/cli/dedent.ts
|
|
933
|
+
function dedent(strings, ...values) {
|
|
934
|
+
const result = strings.reduce((acc, part, index) => acc + part + (values[index] ?? ""), "");
|
|
935
|
+
const lines = result.split(`
|
|
936
|
+
`);
|
|
937
|
+
const indent = lines.filter((line) => line.trim()).reduce((min, line) => Math.min(min, line.match(/^\s*/)[0].length), Number.POSITIVE_INFINITY);
|
|
938
|
+
return lines.map((line) => line.slice(indent)).join(`
|
|
939
|
+
`).trim();
|
|
940
|
+
}
|
|
941
|
+
|
|
942
942
|
// lib/cli/shared/agent-shared.ts
|
|
943
943
|
async function loadAgentInstructions(cwd, fileSystem, agentType) {
|
|
944
944
|
const instructionsPath = join4(cwd, ".dust", "config", "agents", `${agentType}.md`);
|
|
@@ -979,25 +979,6 @@ async function templateVariablesWithInstructions(cwd, fileSystem, settings, hook
|
|
|
979
979
|
agentInstructions
|
|
980
980
|
};
|
|
981
981
|
}
|
|
982
|
-
async function manageGitHooks(dependencies) {
|
|
983
|
-
const { context, fileSystem, settings } = dependencies;
|
|
984
|
-
const hooks = createHooksManager(context.cwd, fileSystem, settings);
|
|
985
|
-
if (!hooks.isGitRepo()) {
|
|
986
|
-
return false;
|
|
987
|
-
}
|
|
988
|
-
const isInstalled = await hooks.isHookInstalled();
|
|
989
|
-
if (!isInstalled) {
|
|
990
|
-
await hooks.installHook();
|
|
991
|
-
return true;
|
|
992
|
-
}
|
|
993
|
-
const hookBinaryPath = await hooks.getHookBinaryPath();
|
|
994
|
-
if (hookBinaryPath && hookBinaryPath !== settings.dustCommand) {
|
|
995
|
-
await hooks.updateHookBinaryPath(settings.dustCommand);
|
|
996
|
-
}
|
|
997
|
-
return true;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
// lib/cli/commands/agent.ts
|
|
1001
982
|
function agentGreeting(vars) {
|
|
1002
983
|
const instructions = vars.agentInstructions ? `
|
|
1003
984
|
---
|
|
@@ -1035,6 +1016,25 @@ ${vars.agentInstructions}` : "";
|
|
|
1035
1016
|
Do NOT proceed without running one of these commands.${instructions}
|
|
1036
1017
|
`;
|
|
1037
1018
|
}
|
|
1019
|
+
async function manageGitHooks(dependencies) {
|
|
1020
|
+
const { context, fileSystem, settings } = dependencies;
|
|
1021
|
+
const hooks = createHooksManager(context.cwd, fileSystem, settings);
|
|
1022
|
+
if (!hooks.isGitRepo()) {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
const isInstalled = await hooks.isHookInstalled();
|
|
1026
|
+
if (!isInstalled) {
|
|
1027
|
+
await hooks.installHook();
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
const hookBinaryPath = await hooks.getHookBinaryPath();
|
|
1031
|
+
if (hookBinaryPath && hookBinaryPath !== settings.dustCommand) {
|
|
1032
|
+
await hooks.updateHookBinaryPath(settings.dustCommand);
|
|
1033
|
+
}
|
|
1034
|
+
return true;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// lib/cli/commands/agent.ts
|
|
1038
1038
|
async function agent(dependencies, env = process.env) {
|
|
1039
1039
|
const { context, fileSystem, settings } = dependencies;
|
|
1040
1040
|
if (env[DUST_SKIP_AGENT] === "1") {
|
|
@@ -6420,6 +6420,7 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
|
|
|
6420
6420
|
}
|
|
6421
6421
|
|
|
6422
6422
|
// lib/lint/validators/content-validator.ts
|
|
6423
|
+
var FRONT_MATTER_DELIMITER = "---";
|
|
6423
6424
|
var REQUIRED_TASK_HEADINGS = ["Task Type", "Blocked By", "Definition of Done"];
|
|
6424
6425
|
var ALLOWED_TASK_TYPES = new Set(VALID_TASK_TYPES);
|
|
6425
6426
|
var MAX_OPENING_SENTENCE_LENGTH = 150;
|
|
@@ -6437,6 +6438,18 @@ var NON_IMPERATIVE_STARTERS = new Set([
|
|
|
6437
6438
|
"you",
|
|
6438
6439
|
"i"
|
|
6439
6440
|
]);
|
|
6441
|
+
function validateNoFrontMatter(artifact) {
|
|
6442
|
+
const firstLine = artifact.rawContent.split(`
|
|
6443
|
+
`)[0];
|
|
6444
|
+
if (firstLine.trim() === FRONT_MATTER_DELIMITER) {
|
|
6445
|
+
return {
|
|
6446
|
+
file: artifact.filePath,
|
|
6447
|
+
line: 1,
|
|
6448
|
+
message: "Artifact must not contain front matter. The title must be the first line."
|
|
6449
|
+
};
|
|
6450
|
+
}
|
|
6451
|
+
return null;
|
|
6452
|
+
}
|
|
6440
6453
|
function validateOpeningSentence(artifact) {
|
|
6441
6454
|
if (!artifact.openingSentence) {
|
|
6442
6455
|
return {
|
|
@@ -6897,6 +6910,40 @@ async function executeTask(task, runParameters, onAgentEvent, context, agentName
|
|
|
6897
6910
|
return "claude_error";
|
|
6898
6911
|
}
|
|
6899
6912
|
}
|
|
6913
|
+
function selectShellRunner(spawnFn, options, loopDeps) {
|
|
6914
|
+
if (options.docker && options.containerRuntime) {
|
|
6915
|
+
return buildContainerShellRunner(spawnFn, options.containerRuntime, options.docker);
|
|
6916
|
+
}
|
|
6917
|
+
return loopDeps.shellRunner ?? defaultShellRunner;
|
|
6918
|
+
}
|
|
6919
|
+
function buildContainerShellRunner(spawnFn, containerRuntime, docker) {
|
|
6920
|
+
const runConfig = {
|
|
6921
|
+
imageTag: docker.imageTag,
|
|
6922
|
+
repoPath: docker.repoPath,
|
|
6923
|
+
homeDir: docker.homeDir,
|
|
6924
|
+
gitProxyUrl: docker.gitProxyUrl
|
|
6925
|
+
};
|
|
6926
|
+
const baseArgs = containerRuntime.buildRunArgs(runConfig);
|
|
6927
|
+
return {
|
|
6928
|
+
run: (command, _cwd) => new Promise((resolve) => {
|
|
6929
|
+
const proc = spawnFn(containerRuntime.runCommand, [
|
|
6930
|
+
...baseArgs,
|
|
6931
|
+
"sh",
|
|
6932
|
+
"-c",
|
|
6933
|
+
command
|
|
6934
|
+
]);
|
|
6935
|
+
const chunks = [];
|
|
6936
|
+
proc.stdout?.on("data", (data) => chunks.push(data.toString()));
|
|
6937
|
+
proc.stderr?.on("data", (data) => chunks.push(data.toString()));
|
|
6938
|
+
proc.on("close", (code) => {
|
|
6939
|
+
resolve({ exitCode: code ?? 1, output: chunks.join("") });
|
|
6940
|
+
});
|
|
6941
|
+
proc.on("error", (error) => {
|
|
6942
|
+
resolve({ exitCode: 1, output: error.message });
|
|
6943
|
+
});
|
|
6944
|
+
})
|
|
6945
|
+
};
|
|
6946
|
+
}
|
|
6900
6947
|
async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, options = {}) {
|
|
6901
6948
|
const { context, fileSystem, settings } = dependencies;
|
|
6902
6949
|
const { spawn: spawn2, run: run2 } = loopDependencies;
|
|
@@ -6946,7 +6993,7 @@ async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAg
|
|
|
6946
6993
|
const taskTitle = task.title ?? task.path;
|
|
6947
6994
|
log2(`found ${tasks.length} task(s), picking: ${taskTitle}`);
|
|
6948
6995
|
onLoopEvent({ type: "loop.tasks_found" });
|
|
6949
|
-
const shellRunner =
|
|
6996
|
+
const shellRunner = selectShellRunner(spawn2, options, loopDependencies);
|
|
6950
6997
|
const preflightResult = await runPreflightChecks(context.cwd, settings.dustCommand, settings.installCommand, shellRunner, onLoopEvent, onAgentEvent, taskTitle);
|
|
6951
6998
|
if (preflightResult.failed) {
|
|
6952
6999
|
return handleCheckFailure(preflightResult.output, settings.dustCommand, { run: run2, prompt: "", spawnOptions, onRawEvent }, onAgentEvent, context, agentName, agentType, logger);
|
|
@@ -11440,6 +11487,8 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
11440
11487
|
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.";
|
|
11441
11488
|
let inOpenQuestions = false;
|
|
11442
11489
|
let currentQuestionLine = null;
|
|
11490
|
+
let currentQuestionText = null;
|
|
11491
|
+
let currentQuestionOptionNames = new Set;
|
|
11443
11492
|
let inOption = false;
|
|
11444
11493
|
let inCodeBlock = false;
|
|
11445
11494
|
for (let i = 0;i < lines.length; i++) {
|
|
@@ -11463,6 +11512,8 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
11463
11512
|
violations.push(...validateH2Heading(filePath, line, i + 1, inOpenQuestions, currentQuestionLine));
|
|
11464
11513
|
inOpenQuestions = line === "## Open Questions";
|
|
11465
11514
|
currentQuestionLine = null;
|
|
11515
|
+
currentQuestionText = null;
|
|
11516
|
+
currentQuestionOptionNames = new Set;
|
|
11466
11517
|
inOption = false;
|
|
11467
11518
|
inCodeBlock = false;
|
|
11468
11519
|
continue;
|
|
@@ -11478,6 +11529,7 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
11478
11529
|
line: currentQuestionLine
|
|
11479
11530
|
});
|
|
11480
11531
|
}
|
|
11532
|
+
currentQuestionOptionNames = new Set;
|
|
11481
11533
|
if (!trimmedLine.endsWith("?")) {
|
|
11482
11534
|
violations.push({
|
|
11483
11535
|
file: filePath,
|
|
@@ -11485,12 +11537,24 @@ function validateIdeaOpenQuestions(artifact) {
|
|
|
11485
11537
|
line: i + 1
|
|
11486
11538
|
});
|
|
11487
11539
|
currentQuestionLine = null;
|
|
11540
|
+
currentQuestionText = null;
|
|
11488
11541
|
} else {
|
|
11489
11542
|
currentQuestionLine = i + 1;
|
|
11543
|
+
currentQuestionText = trimmedLine.slice(4);
|
|
11490
11544
|
}
|
|
11491
11545
|
continue;
|
|
11492
11546
|
}
|
|
11493
11547
|
if (line.startsWith("#### ")) {
|
|
11548
|
+
const optionName = trimmedLine.slice(5);
|
|
11549
|
+
if (currentQuestionOptionNames.has(optionName)) {
|
|
11550
|
+
violations.push({
|
|
11551
|
+
file: filePath,
|
|
11552
|
+
message: `Duplicate option "${optionName}" under question "${currentQuestionText}" — each option must have a unique name`,
|
|
11553
|
+
line: i + 1
|
|
11554
|
+
});
|
|
11555
|
+
} else {
|
|
11556
|
+
currentQuestionOptionNames.add(optionName);
|
|
11557
|
+
}
|
|
11494
11558
|
currentQuestionLine = null;
|
|
11495
11559
|
inOption = true;
|
|
11496
11560
|
continue;
|
|
@@ -11866,6 +11930,9 @@ function validateArtifacts(context) {
|
|
|
11866
11930
|
}
|
|
11867
11931
|
for (const artifacts of Object.values(byType)) {
|
|
11868
11932
|
for (const artifact of artifacts) {
|
|
11933
|
+
const frontMatterViolation = validateNoFrontMatter(artifact);
|
|
11934
|
+
if (frontMatterViolation)
|
|
11935
|
+
violations.push(frontMatterViolation);
|
|
11869
11936
|
const openingSentenceViolation = validateOpeningSentence(artifact);
|
|
11870
11937
|
if (openingSentenceViolation)
|
|
11871
11938
|
violations.push(openingSentenceViolation);
|
|
@@ -12214,6 +12281,75 @@ async function check(dependencies, shellRunner, clock, _setInterval, _clearInter
|
|
|
12214
12281
|
return { exitCode };
|
|
12215
12282
|
}
|
|
12216
12283
|
|
|
12284
|
+
// lib/cli/commands/codex-hook.ts
|
|
12285
|
+
var KNOWN_HOOK_EVENTS = [
|
|
12286
|
+
"PreToolUse",
|
|
12287
|
+
"PermissionRequest",
|
|
12288
|
+
"PostToolUse",
|
|
12289
|
+
"SessionStart",
|
|
12290
|
+
"UserPromptSubmit",
|
|
12291
|
+
"Stop"
|
|
12292
|
+
];
|
|
12293
|
+
async function readStdinUtf8() {
|
|
12294
|
+
const chunks = [];
|
|
12295
|
+
for await (const chunk of process.stdin) {
|
|
12296
|
+
chunks.push(chunk);
|
|
12297
|
+
}
|
|
12298
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
12299
|
+
}
|
|
12300
|
+
var defaultCodexHookDependencies = {
|
|
12301
|
+
readStdin: readStdinUtf8
|
|
12302
|
+
};
|
|
12303
|
+
function isKnownEvent(value) {
|
|
12304
|
+
return typeof value === "string" && KNOWN_HOOK_EVENTS.includes(value);
|
|
12305
|
+
}
|
|
12306
|
+
async function handleSessionStart(dependencies) {
|
|
12307
|
+
const { context, fileSystem, settings } = dependencies;
|
|
12308
|
+
const agentInstructions = await loadAgentInstructions(context.cwd, fileSystem, "codex");
|
|
12309
|
+
const additionalContext = agentGreeting({
|
|
12310
|
+
bin: settings.dustCommand,
|
|
12311
|
+
agentName: "Codex",
|
|
12312
|
+
hooksInstalled: false,
|
|
12313
|
+
isClaudeCodeWeb: false,
|
|
12314
|
+
hasIdeaFile: true,
|
|
12315
|
+
agentInstructions
|
|
12316
|
+
});
|
|
12317
|
+
return JSON.stringify({
|
|
12318
|
+
continue: true,
|
|
12319
|
+
hookSpecificOutput: {
|
|
12320
|
+
hookEventName: "SessionStart",
|
|
12321
|
+
additionalContext
|
|
12322
|
+
},
|
|
12323
|
+
systemMessage: "dust agent loaded"
|
|
12324
|
+
});
|
|
12325
|
+
}
|
|
12326
|
+
function handleNoOp() {
|
|
12327
|
+
return JSON.stringify({ continue: true });
|
|
12328
|
+
}
|
|
12329
|
+
async function codexHook(dependencies, hookDependencies = defaultCodexHookDependencies) {
|
|
12330
|
+
const { context } = dependencies;
|
|
12331
|
+
const raw = await hookDependencies.readStdin();
|
|
12332
|
+
let payload;
|
|
12333
|
+
try {
|
|
12334
|
+
payload = JSON.parse(raw);
|
|
12335
|
+
} catch {
|
|
12336
|
+
context.stderr("dust codex hook: failed to parse stdin as JSON");
|
|
12337
|
+
return { exitCode: 1 };
|
|
12338
|
+
}
|
|
12339
|
+
if (!payload || typeof payload !== "object") {
|
|
12340
|
+
context.stderr("dust codex hook: stdin payload must be a JSON object");
|
|
12341
|
+
return { exitCode: 1 };
|
|
12342
|
+
}
|
|
12343
|
+
const eventName = payload.hook_event_name;
|
|
12344
|
+
if (!isKnownEvent(eventName)) {
|
|
12345
|
+
context.stderr(`dust codex hook: unknown hook_event_name: ${JSON.stringify(eventName)}`);
|
|
12346
|
+
return { exitCode: 1 };
|
|
12347
|
+
}
|
|
12348
|
+
const response = eventName === "SessionStart" ? await handleSessionStart(dependencies) : handleNoOp();
|
|
12349
|
+
context.stdout(response);
|
|
12350
|
+
return { exitCode: 0 };
|
|
12351
|
+
}
|
|
12352
|
+
|
|
12217
12353
|
// lib/bundled-core-principles.ts
|
|
12218
12354
|
var BUNDLED_PRINCIPLES = [
|
|
12219
12355
|
{
|
|
@@ -13857,16 +13993,15 @@ async function init(dependencies) {
|
|
|
13857
13993
|
throw error;
|
|
13858
13994
|
}
|
|
13859
13995
|
}
|
|
13860
|
-
const runner = dustCommand.split(" ")[0];
|
|
13861
13996
|
context.stdout("");
|
|
13862
13997
|
context.stdout(`${colors.bold}\uD83D\uDE80 Next steps:${colors.reset} Commit the changes if you are happy, then get planning!`);
|
|
13863
13998
|
context.stdout("");
|
|
13864
13999
|
context.stdout(`${colors.dim}If this is a new repository, you can start adding ideas or tasks right away:${colors.reset}`);
|
|
13865
|
-
context.stdout(` ${colors.cyan}>${colors.reset}
|
|
13866
|
-
context.stdout(` ${colors.cyan}>${colors.reset}
|
|
14000
|
+
context.stdout(` ${colors.cyan}>${colors.reset} claude "Idea: friendly UI for non-technical users"`);
|
|
14001
|
+
context.stdout(` ${colors.cyan}>${colors.reset} codex "Task: set up code coverage"`);
|
|
13867
14002
|
context.stdout("");
|
|
13868
14003
|
context.stdout(`${colors.dim}If this is an existing codebase, you might want to backfill principles and facts:${colors.reset}`);
|
|
13869
|
-
context.stdout(` ${colors.cyan}>${colors.reset}
|
|
14004
|
+
context.stdout(` ${colors.cyan}>${colors.reset} claude "Add principles and facts based on the code in this repository"`);
|
|
13870
14005
|
return { exitCode: 0 };
|
|
13871
14006
|
}
|
|
13872
14007
|
|
|
@@ -14377,7 +14512,8 @@ async function runLoop(dependencies, loopDependencies) {
|
|
|
14377
14512
|
let completedIterations = 0;
|
|
14378
14513
|
const iterationOptions = {
|
|
14379
14514
|
hooksInstalled,
|
|
14380
|
-
docker: dockerConfig
|
|
14515
|
+
docker: dockerConfig,
|
|
14516
|
+
containerRuntime
|
|
14381
14517
|
};
|
|
14382
14518
|
if (eventsUrl) {
|
|
14383
14519
|
iterationOptions.onRawEvent = createHeartbeatThrottler(onAgentEvent, loopDependencies.agentType ?? "claude");
|
|
@@ -14861,6 +14997,9 @@ function runLoopClaude(commandDependencies) {
|
|
|
14861
14997
|
function runLoopCodex(commandDependencies) {
|
|
14862
14998
|
return loopCodex(commandDependencies, createCodexDependencies());
|
|
14863
14999
|
}
|
|
15000
|
+
function runCodexHook(commandDependencies) {
|
|
15001
|
+
return codexHook(commandDependencies, defaultCodexHookDependencies);
|
|
15002
|
+
}
|
|
14864
15003
|
var commandRegistry = {
|
|
14865
15004
|
init,
|
|
14866
15005
|
lint: lintMarkdown,
|
|
@@ -14875,6 +15014,7 @@ var commandRegistry = {
|
|
|
14875
15014
|
audit,
|
|
14876
15015
|
"bucket worker": bucketWorker,
|
|
14877
15016
|
"bucket tool": bucketTool,
|
|
15017
|
+
"codex hook": runCodexHook,
|
|
14878
15018
|
"core principle": corePrinciple,
|
|
14879
15019
|
focus,
|
|
14880
15020
|
"new task": newTask,
|
|
@@ -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);
|