@joshski/dust 0.1.54 → 0.1.56

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.
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Agent detection module
3
+ *
4
+ * Provides type-safe detection of which agent environment is running
5
+ * based on environment variables.
6
+ */
7
+ export type Agent = {
8
+ type: 'claude-code-web';
9
+ name: 'Claude Code Web';
10
+ } | {
11
+ type: 'claude-code';
12
+ name: 'Claude Code';
13
+ } | {
14
+ type: 'codex';
15
+ name: 'Codex';
16
+ } | {
17
+ type: 'unknown';
18
+ name: 'Agent';
19
+ };
20
+ export type AgentType = Agent['type'];
21
+ /**
22
+ * Detects which agent environment is running based on environment variables.
23
+ *
24
+ * Detection priority:
25
+ * 1. CLAUDECODE + CLAUDE_CODE_REMOTE → Claude Code Web
26
+ * 2. CLAUDECODE alone → Claude Code
27
+ * 3. CODEX_HOME or CODEX_CI → Codex
28
+ * 4. Fallback → unknown Agent
29
+ */
30
+ export declare function detectAgent(env?: NodeJS.ProcessEnv): Agent;
package/dist/agents.js ADDED
@@ -0,0 +1,16 @@
1
+ // lib/agents/detection.ts
2
+ function detectAgent(env = process.env) {
3
+ if (env.CLAUDECODE) {
4
+ if (env.CLAUDE_CODE_REMOTE) {
5
+ return { type: "claude-code-web", name: "Claude Code Web" };
6
+ }
7
+ return { type: "claude-code", name: "Claude Code" };
8
+ }
9
+ if (env.CODEX_HOME || env.CODEX_CI) {
10
+ return { type: "codex", name: "Codex" };
11
+ }
12
+ return { type: "unknown", name: "Agent" };
13
+ }
14
+ export {
15
+ detectAgent
16
+ };
@@ -4,6 +4,7 @@
4
4
  export interface CommandContext {
5
5
  cwd: string;
6
6
  stdout: (message: string) => void;
7
+ stdoutInline?: (message: string) => void;
7
8
  stderr: (message: string) => void;
8
9
  }
9
10
  export interface CommandResult {
package/dist/dust.js CHANGED
@@ -269,7 +269,7 @@ function detectAgent(env = process.env) {
269
269
  }
270
270
  return { type: "claude-code", name: "Claude Code" };
271
271
  }
272
- if (env.CODEX_HOME) {
272
+ if (env.CODEX_HOME || env.CODEX_CI) {
273
273
  return { type: "codex", name: "Codex" };
274
274
  }
275
275
  return { type: "unknown", name: "Agent" };
@@ -1925,8 +1925,7 @@ function formatLoopEvent(event) {
1925
1925
  case "loop.checking_tasks":
1926
1926
  return null;
1927
1927
  case "loop.no_tasks":
1928
- return `\uD83D\uDE34 No tasks available. Sleeping...
1929
- `;
1928
+ return "\uD83D\uDE34 No tasks available. Sleeping...";
1930
1929
  case "loop.tasks_found":
1931
1930
  return `✨ Found a task. Going to work!
1932
1931
  `;
@@ -1973,7 +1972,18 @@ function createWireEventSender(eventsUrl, sessionId, postEvent, onError, getAgen
1973
1972
  }
1974
1973
  var log = createLogger("dust.cli.commands.loop");
1975
1974
  var SLEEP_INTERVAL_MS = 30000;
1975
+ var SLEEP_STEP_MS = 1000;
1976
1976
  var DEFAULT_MAX_ITERATIONS = 10;
1977
+ async function sleepWithProgress(sleep, totalMs, writeInline, writeLine) {
1978
+ let remainingMs = totalMs;
1979
+ while (remainingMs > 0) {
1980
+ const stepMs = Math.min(SLEEP_STEP_MS, remainingMs);
1981
+ await sleep(stepMs);
1982
+ writeInline(".");
1983
+ remainingMs -= stepMs;
1984
+ }
1985
+ writeLine("");
1986
+ }
1977
1987
  async function gitPull(cwd, spawn) {
1978
1988
  return new Promise((resolve) => {
1979
1989
  const proc = spawn("git", ["pull"], {
@@ -2172,7 +2182,8 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
2172
2182
  const result = await runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, iterationOptions);
2173
2183
  if (result === "no_tasks") {
2174
2184
  log("sleeping, no tasks");
2175
- await loopDependencies.sleep(SLEEP_INTERVAL_MS);
2185
+ const writeInline = context.stdoutInline ?? context.stdout;
2186
+ await sleepWithProgress(loopDependencies.sleep, SLEEP_INTERVAL_MS, writeInline, context.stdout);
2176
2187
  } else {
2177
2188
  completedIterations++;
2178
2189
  log(`iteration ${completedIterations}/${maxIterations} complete, result=${result}`);
@@ -3706,17 +3717,15 @@ function validateIdeaOpenQuestions(filePath, content) {
3706
3717
  const violations = [];
3707
3718
  const lines = content.split(`
3708
3719
  `);
3720
+ 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.";
3709
3721
  let inOpenQuestions = false;
3710
3722
  let currentQuestionLine = null;
3723
+ let inOption = false;
3711
3724
  let inCodeBlock = false;
3712
3725
  for (let i = 0;i < lines.length; i++) {
3713
3726
  const line = lines[i];
3714
- if (line.startsWith("```")) {
3715
- inCodeBlock = !inCodeBlock;
3716
- continue;
3717
- }
3718
- if (inCodeBlock)
3719
- continue;
3727
+ const trimmedLine = line.trimEnd();
3728
+ const nonWhitespaceLine = line.trim();
3720
3729
  if (line.startsWith("## ")) {
3721
3730
  if (inOpenQuestions && currentQuestionLine !== null) {
3722
3731
  violations.push({
@@ -3735,19 +3744,27 @@ function validateIdeaOpenQuestions(filePath, content) {
3735
3744
  }
3736
3745
  inOpenQuestions = line === "## Open Questions";
3737
3746
  currentQuestionLine = null;
3747
+ inOption = false;
3748
+ inCodeBlock = false;
3738
3749
  continue;
3739
3750
  }
3740
3751
  if (!inOpenQuestions)
3741
3752
  continue;
3742
- if (/^[-*] /.test(line.trimStart())) {
3743
- violations.push({
3744
- file: filePath,
3745
- message: "Open Questions must use ### headings for questions and #### headings for options, not bullet points. Run `dust new idea` to see the expected format.",
3746
- line: i + 1
3747
- });
3753
+ if (line.startsWith("```")) {
3754
+ if (!inOption && !inCodeBlock) {
3755
+ violations.push({
3756
+ file: filePath,
3757
+ message: topLevelStructureMessage,
3758
+ line: i + 1
3759
+ });
3760
+ }
3761
+ inCodeBlock = !inCodeBlock;
3748
3762
  continue;
3749
3763
  }
3764
+ if (inCodeBlock)
3765
+ continue;
3750
3766
  if (line.startsWith("### ")) {
3767
+ inOption = false;
3751
3768
  if (currentQuestionLine !== null) {
3752
3769
  violations.push({
3753
3770
  file: filePath,
@@ -3755,7 +3772,7 @@ function validateIdeaOpenQuestions(filePath, content) {
3755
3772
  line: currentQuestionLine
3756
3773
  });
3757
3774
  }
3758
- if (!line.trimEnd().endsWith("?")) {
3775
+ if (!trimmedLine.endsWith("?")) {
3759
3776
  violations.push({
3760
3777
  file: filePath,
3761
3778
  message: 'Questions must end with "?" (e.g., "### Should we take our own payments?")',
@@ -3769,6 +3786,15 @@ function validateIdeaOpenQuestions(filePath, content) {
3769
3786
  }
3770
3787
  if (line.startsWith("#### ")) {
3771
3788
  currentQuestionLine = null;
3789
+ inOption = true;
3790
+ continue;
3791
+ }
3792
+ if (nonWhitespaceLine && !inOption) {
3793
+ violations.push({
3794
+ file: filePath,
3795
+ message: topLevelStructureMessage,
3796
+ line: i + 1
3797
+ });
3772
3798
  }
3773
3799
  }
3774
3800
  if (inOpenQuestions && currentQuestionLine !== null) {
@@ -5125,6 +5151,7 @@ async function wireEntry(fsPrimitives, processPrimitives, consolePrimitives) {
5125
5151
  context: {
5126
5152
  cwd: processPrimitives.cwd(),
5127
5153
  stdout: consolePrimitives.log,
5154
+ stdoutInline: consolePrimitives.write,
5128
5155
  stderr: consolePrimitives.error
5129
5156
  },
5130
5157
  fileSystem,
@@ -5140,4 +5167,10 @@ await wireEntry({ existsSync, statSync: statSync2, readFile: readFile2, writeFil
5140
5167
  exit: (code) => {
5141
5168
  process.exitCode = code;
5142
5169
  }
5143
- }, { log: console.log, error: console.error });
5170
+ }, {
5171
+ log: console.log,
5172
+ write: (message) => {
5173
+ process.stdout.write(message);
5174
+ },
5175
+ error: console.error
5176
+ });
@@ -111,9 +111,10 @@ async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSen
111
111
  return { filePath };
112
112
  }
113
113
  async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description) {
114
- return createIdeaTask(fileSystem, dustPath, "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Review \`.dust/goals/\` for alignment and \`.dust/facts/\` for relevant design decisions. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
114
+ return createIdeaTask(fileSystem, dustPath, "Refine Idea: ", ideaSlug, (ideaTitle) => `Thoroughly research this idea and refine it into a well-defined proposal. Read the idea file, explore the codebase for relevant context, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Review \`.dust/goals/\` for alignment and \`.dust/facts/\` for relevant design decisions. See [${ideaTitle}](../ideas/${ideaSlug}.md). If you add open questions, use \`## Open Questions\` with \`### Question?\` headings and one or more \`#### Option\` headings beneath each question, and only add questions that are meaningful decisions worth asking.`, [
115
115
  "Idea is thoroughly researched with relevant codebase context",
116
116
  "Open questions are added for any ambiguous or underspecified aspects",
117
+ "Open questions follow the required heading format and focus on high-value decisions",
117
118
  "Idea file is updated with findings"
118
119
  ], { description });
119
120
  }
@@ -174,7 +175,7 @@ ${description}
174
175
  const ideaPath = `.dust/ideas/${ideaFilename}`;
175
176
  const content = `# ${taskTitle}
176
177
 
177
- Research this idea thoroughly, then create an idea file at \`${ideaPath}\`. Read the codebase for relevant context, flesh out the description, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. Review \`.dust/goals/\` and \`.dust/facts/\` for relevant context.
178
+ Research this idea thoroughly, then create an idea file at \`${ideaPath}\`. Read the codebase for relevant context, flesh out the description, and identify any ambiguity. Where aspects are unclear or could go multiple ways, add open questions to the idea file. If you add open questions, use \`## Open Questions\` with \`### Question?\` headings and one or more \`#### Option\` headings beneath each question, and only add questions that are meaningful decisions worth asking. Review \`.dust/goals/\` and \`.dust/facts/\` for relevant context.
178
179
 
179
180
  ## Idea Description
180
181
 
@@ -194,6 +195,7 @@ ${description}
194
195
  - [ ] Idea file has an H1 title matching "${title}"
195
196
  - [ ] Idea includes relevant context from codebase exploration
196
197
  - [ ] Open questions are added for any ambiguous or underspecified aspects
198
+ - [ ] Open questions follow the required heading format and focus on high-value decisions
197
199
  `;
198
200
  await fileSystem.writeFile(filePath, content);
199
201
  return { filePath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,6 +18,10 @@
18
18
  "import": "./dist/logging.js",
19
19
  "types": "./dist/logging/index.d.ts"
20
20
  },
21
+ "./agents": {
22
+ "import": "./dist/agents.js",
23
+ "types": "./dist/agents/detection.d.ts"
24
+ },
21
25
  "./istanbul/minimal-reporter": "./lib/istanbul/minimal-reporter.cjs"
22
26
  },
23
27
  "files": [
@@ -38,7 +42,7 @@
38
42
  "author": "joshski",
39
43
  "license": "MIT",
40
44
  "scripts": {
41
- "build": "bun build lib/cli/run.ts --target node --outfile dist/dust.js && printf '%s\\n%s' '#!/usr/bin/env node' \"$(cat dist/dust.js)\" > dist/dust.js && bun build lib/workflow-tasks.ts --target node --outfile dist/workflow-tasks.js && bun build lib/logging/index.ts --target node --outfile dist/logging.js && bunx tsc --project tsconfig.build.json",
45
+ "build": "bun build lib/cli/run.ts --target node --outfile dist/dust.js && printf '%s\\n%s' '#!/usr/bin/env node' \"$(cat dist/dust.js)\" > dist/dust.js && bun build lib/workflow-tasks.ts --target node --outfile dist/workflow-tasks.js && bun build lib/logging/index.ts --target node --outfile dist/logging.js && bun build lib/agents/detection.ts --target node --outfile dist/agents.js && bunx tsc --project tsconfig.build.json",
42
46
  "test": "vitest run",
43
47
  "test:coverage": "vitest run --coverage",
44
48
  "eval": "bun run ./evals/run.ts"