@joshski/dust 0.1.45 → 0.1.47

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.
@@ -39,8 +39,4 @@ export interface EventMessage {
39
39
  * is forwarded as a claude-event.
40
40
  */
41
41
  export declare function rawEventToAgentEvent(rawEvent: Record<string, unknown>): AgentSessionEvent;
42
- /**
43
- * Format an AgentSessionEvent for console output.
44
- * Returns null for events that should not be displayed.
45
- */
46
42
  export declare function formatAgentEvent(event: AgentSessionEvent): string | null;
@@ -22,6 +22,7 @@ export interface FileSystem {
22
22
  readdir: (path: string) => Promise<string[]>;
23
23
  chmod: (path: string, mode: number) => Promise<void>;
24
24
  isDirectory: (path: string) => boolean;
25
+ getFileCreationTime: (path: string) => number;
25
26
  }
26
27
  export interface GlobScanner {
27
28
  scan: (dir: string) => AsyncIterable<string>;
package/dist/dust.js CHANGED
@@ -577,6 +577,10 @@ function agentDeveloperExperience() {
577
577
  4. **Debugging tools** - Can agents diagnose issues without trial and error?
578
578
  5. **Structured logging** - Is system behavior observable through logs?
579
579
 
580
+ ## Goals
581
+
582
+ (none)
583
+
580
584
  ## Blocked By
581
585
 
582
586
  (none)
@@ -606,6 +610,10 @@ function deadCode() {
606
610
  4. **Unused dependencies** - Packages in package.json not used in code
607
611
  5. **Commented-out code** - Old code left in comments
608
612
 
613
+ ## Goals
614
+
615
+ (none)
616
+
609
617
  ## Blocked By
610
618
 
611
619
  (none)
@@ -636,6 +644,10 @@ function factsVerification() {
636
644
  3. **Staleness** - Have facts become outdated due to recent changes?
637
645
  4. **Relevance** - Are all facts still useful for understanding the project?
638
646
 
647
+ ## Goals
648
+
649
+ (none)
650
+
639
651
  ## Blocked By
640
652
 
641
653
  (none)
@@ -665,6 +677,10 @@ function ideasFromCommits() {
665
677
  3. **Pattern opportunities** - Can recent changes be generalized?
666
678
  4. **Test gaps** - Do recent changes have adequate test coverage?
667
679
 
680
+ ## Goals
681
+
682
+ (none)
683
+
668
684
  ## Blocked By
669
685
 
670
686
  (none)
@@ -693,6 +709,10 @@ function ideasFromGoals() {
693
709
  3. **New opportunities** - What work would better achieve each goal?
694
710
  4. **Goal alignment** - Are current tasks aligned with stated goals?
695
711
 
712
+ ## Goals
713
+
714
+ (none)
715
+
696
716
  ## Blocked By
697
717
 
698
718
  (none)
@@ -721,6 +741,10 @@ function performanceReview() {
721
741
  4. **Build performance** - How fast is the build process?
722
742
  5. **Test speed** - Are tests running efficiently?
723
743
 
744
+ ## Goals
745
+
746
+ (none)
747
+
724
748
  ## Blocked By
725
749
 
726
750
  (none)
@@ -750,6 +774,10 @@ function securityReview() {
750
774
  4. **Sensitive data exposure** - Logging sensitive data, insecure storage
751
775
  5. **Dependency vulnerabilities** - Known CVEs in dependencies
752
776
 
777
+ ## Goals
778
+
779
+ (none)
780
+
753
781
  ## Blocked By
754
782
 
755
783
  (none)
@@ -780,6 +808,10 @@ function staleIdeas() {
780
808
  3. **Actionability** - Can the idea be converted to a task?
781
809
  4. **Duplication** - Are there overlapping or redundant ideas?
782
810
 
811
+ ## Goals
812
+
813
+ (none)
814
+
783
815
  ## Blocked By
784
816
 
785
817
  (none)
@@ -809,6 +841,10 @@ function testCoverage() {
809
841
  4. **User-facing features** - UI components, form validation
810
842
  5. **Recent changes** - Code modified in the last few commits
811
843
 
844
+ ## Goals
845
+
846
+ (none)
847
+
812
848
  ## Blocked By
813
849
 
814
850
  (none)
@@ -1593,12 +1629,19 @@ function rawEventToAgentEvent(rawEvent) {
1593
1629
  }
1594
1630
  return { type: "claude-event", rawEvent };
1595
1631
  }
1632
+ function agentDisplayName(agentType) {
1633
+ if (agentType === "codex")
1634
+ return "Codex";
1635
+ return "Claude";
1636
+ }
1596
1637
  function formatAgentEvent(event) {
1597
1638
  switch (event.type) {
1598
- case "agent-session-started":
1599
- return `\uD83E\uDD16 Starting Claude: ${event.title}`;
1639
+ case "agent-session-started": {
1640
+ const name = agentDisplayName(event.agentType);
1641
+ return `\uD83E\uDD16 Starting ${name}: ${event.title}`;
1642
+ }
1600
1643
  case "agent-session-ended":
1601
- return event.success ? "\uD83E\uDD16 Claude session ended (success)" : `\uD83E\uDD16 Claude session ended (error: ${event.error})`;
1644
+ return event.success ? "\uD83E\uDD16 Agent session ended (success)" : `\uD83E\uDD16 Agent session ended (error: ${event.error})`;
1602
1645
  case "agent-session-activity":
1603
1646
  case "claude-event":
1604
1647
  return null;
@@ -1699,7 +1742,11 @@ async function findUnblockedTasks(cwd, fileSystem) {
1699
1742
  return { tasks: [] };
1700
1743
  }
1701
1744
  const files = await fileSystem.readdir(tasksPath);
1702
- const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
1745
+ const mdFiles = files.filter((f) => f.endsWith(".md")).sort((a, b) => {
1746
+ const aTime = fileSystem.getFileCreationTime(`${tasksPath}/${a}`);
1747
+ const bTime = fileSystem.getFileCreationTime(`${tasksPath}/${b}`);
1748
+ return aTime - bTime;
1749
+ });
1703
1750
  if (mdFiles.length === 0) {
1704
1751
  return { tasks: [] };
1705
1752
  }
@@ -1777,8 +1824,10 @@ function formatLoopEvent(event) {
1777
1824
  switch (event.type) {
1778
1825
  case "loop.warning":
1779
1826
  return "⚠️ WARNING: This command skips all permission checks. Only use in a sandbox environment!";
1780
- case "loop.started":
1781
- return `\uD83D\uDD04 Starting dust loop claude (max ${event.maxIterations} iterations)...`;
1827
+ case "loop.started": {
1828
+ const agent2 = event.agentType ?? "claude";
1829
+ return `\uD83D\uDD04 Starting dust loop ${agent2} (max ${event.maxIterations} iterations)...`;
1830
+ }
1782
1831
  case "loop.syncing":
1783
1832
  return "\uD83C\uDF0D Syncing with remote";
1784
1833
  case "loop.sync_skipped":
@@ -1864,7 +1913,8 @@ async function findAvailableTasks(dependencies) {
1864
1913
  async function runOneIteration(dependencies, loopDependencies, onLoopEvent, onAgentEvent, options = {}) {
1865
1914
  const { context } = dependencies;
1866
1915
  const { spawn, run: run2 } = loopDependencies;
1867
- const { onRawEvent } = options;
1916
+ const agentName = loopDependencies.agentType === "codex" ? "Codex" : "Claude";
1917
+ const { onRawEvent, hooksInstalled = false } = options;
1868
1918
  onLoopEvent({ type: "loop.syncing" });
1869
1919
  const pullResult = await gitPull(context.cwd, spawn);
1870
1920
  if (!pullResult.success) {
@@ -1888,7 +1938,7 @@ Make sure the repository is in a clean state and synced with remote before finis
1888
1938
  type: "agent-session-started",
1889
1939
  title: "Resolving git conflict",
1890
1940
  prompt: prompt2,
1891
- agentType: "claude",
1941
+ agentType: loopDependencies.agentType ?? "claude",
1892
1942
  purpose: "git-conflict",
1893
1943
  ...getEnvironmentContext(context.cwd)
1894
1944
  });
@@ -1905,7 +1955,7 @@ Make sure the repository is in a clean state and synced with remote before finis
1905
1955
  return "resolved_pull_conflict";
1906
1956
  } catch (error) {
1907
1957
  const errorMessage = error instanceof Error ? error.message : String(error);
1908
- context.stderr(`Claude failed to resolve git pull conflict: ${errorMessage}`);
1958
+ context.stderr(`${agentName} failed to resolve git pull conflict: ${errorMessage}`);
1909
1959
  onAgentEvent?.({
1910
1960
  type: "agent-session-ended",
1911
1961
  success: false,
@@ -1923,7 +1973,7 @@ Make sure the repository is in a clean state and synced with remote before finis
1923
1973
  onLoopEvent({ type: "loop.tasks_found" });
1924
1974
  const taskContent = await dependencies.fileSystem.readFile(`${dependencies.context.cwd}/${task.path}`);
1925
1975
  const { dustCommand, installCommand = "npm install" } = dependencies.settings;
1926
- const instructions = buildImplementationInstructions(dustCommand, true, task.title ?? undefined);
1976
+ const instructions = buildImplementationInstructions(dustCommand, hooksInstalled, task.title ?? undefined);
1927
1977
  const prompt = `Run \`${installCommand}\` to install dependencies, then implement the following task.
1928
1978
 
1929
1979
  The following is the contents of the task file \`${task.path}\`:
@@ -1941,7 +1991,7 @@ ${instructions}`;
1941
1991
  type: "agent-session-started",
1942
1992
  title: task.title ?? task.path,
1943
1993
  prompt,
1944
- agentType: "claude",
1994
+ agentType: loopDependencies.agentType ?? "claude",
1945
1995
  purpose: "task",
1946
1996
  ...getEnvironmentContext(context.cwd)
1947
1997
  });
@@ -1958,7 +2008,7 @@ ${instructions}`;
1958
2008
  return "ran_claude";
1959
2009
  } catch (error) {
1960
2010
  const errorMessage = error instanceof Error ? error.message : String(error);
1961
- context.stderr(`Claude exited with error: ${errorMessage}`);
2011
+ context.stderr(`${agentName} exited with error: ${errorMessage}`);
1962
2012
  onAgentEvent?.({
1963
2013
  type: "agent-session-ended",
1964
2014
  success: false,
@@ -2001,12 +2051,17 @@ async function loopClaude(dependencies, loopDependencies = createDefaultDependen
2001
2051
  }
2002
2052
  sendWireEvent(event);
2003
2053
  };
2054
+ const hooksInstalled = await manageGitHooks(dependencies);
2004
2055
  onLoopEvent({ type: "loop.warning" });
2005
- onLoopEvent({ type: "loop.started", maxIterations });
2056
+ onLoopEvent({
2057
+ type: "loop.started",
2058
+ maxIterations,
2059
+ agentType: loopDependencies.agentType
2060
+ });
2006
2061
  context.stdout(" Press Ctrl+C to stop");
2007
2062
  context.stdout("");
2008
2063
  let completedIterations = 0;
2009
- const iterationOptions = {};
2064
+ const iterationOptions = { hooksInstalled };
2010
2065
  if (eventsUrl) {
2011
2066
  iterationOptions.onRawEvent = (rawEvent) => {
2012
2067
  onAgentEvent(rawEventToAgentEvent(rawEvent));
@@ -2117,9 +2172,11 @@ async function runRepositoryLoop(repoState, repoDeps, sendEvent, sessionId) {
2117
2172
  sendEvent(msg);
2118
2173
  }
2119
2174
  };
2175
+ const hooksInstalled = await manageGitHooks(commandDeps);
2120
2176
  while (!repoState.stopRequested) {
2121
2177
  agentSessionId = crypto.randomUUID();
2122
2178
  const result = await runOneIteration(commandDeps, loopDeps, onLoopEvent, onAgentEvent, {
2179
+ hooksInstalled,
2123
2180
  onRawEvent: (rawEvent) => {
2124
2181
  onAgentEvent(rawEventToAgentEvent(rawEvent));
2125
2182
  }
@@ -2751,6 +2808,7 @@ function createDefaultBucketDependencies() {
2751
2808
  return false;
2752
2809
  }
2753
2810
  },
2811
+ getFileCreationTime: (path) => statSync(path).birthtimeMs,
2754
2812
  readFile: (path) => readFile(path, "utf8"),
2755
2813
  writeFile: (path, content) => writeFile(path, content, "utf8"),
2756
2814
  mkdir: (path, options) => mkdir(path, options).then(() => {}),
@@ -4281,6 +4339,125 @@ async function list(dependencies) {
4281
4339
  return { exitCode: 0 };
4282
4340
  }
4283
4341
 
4342
+ // lib/codex/spawn-codex.ts
4343
+ import { spawn as nodeSpawn4 } from "node:child_process";
4344
+ import { createInterface as nodeCreateInterface2 } from "node:readline";
4345
+ var defaultDependencies2 = {
4346
+ spawn: nodeSpawn4,
4347
+ createInterface: nodeCreateInterface2
4348
+ };
4349
+ async function* spawnCodex(prompt, options = {}, dependencies = defaultDependencies2) {
4350
+ const { cwd, env } = options;
4351
+ const codexArguments = ["exec", prompt, "--json", "--yolo"];
4352
+ if (cwd) {
4353
+ codexArguments.push("--cd", cwd);
4354
+ }
4355
+ const proc = dependencies.spawn("codex", codexArguments, {
4356
+ stdio: ["ignore", "pipe", "pipe"],
4357
+ env: { ...process.env, ...env }
4358
+ });
4359
+ if (!proc.stdout) {
4360
+ throw new Error("Failed to get stdout from codex process");
4361
+ }
4362
+ const rl = dependencies.createInterface({ input: proc.stdout });
4363
+ for await (const line of rl) {
4364
+ if (!line.trim())
4365
+ continue;
4366
+ try {
4367
+ yield JSON.parse(line);
4368
+ } catch {}
4369
+ }
4370
+ let stderrOutput = "";
4371
+ proc.stderr?.on("data", (data) => {
4372
+ stderrOutput += data.toString();
4373
+ });
4374
+ await new Promise((resolve2, reject) => {
4375
+ proc.on("close", (code) => {
4376
+ if (code === 0 || code === null)
4377
+ resolve2();
4378
+ else {
4379
+ const errMsg = stderrOutput.trim() ? `codex exited with code ${code}: ${stderrOutput.trim()}` : `codex exited with code ${code}`;
4380
+ reject(new Error(errMsg));
4381
+ }
4382
+ });
4383
+ proc.on("error", reject);
4384
+ });
4385
+ }
4386
+
4387
+ // lib/codex/event-parser.ts
4388
+ function* parseCodexRawEvent(raw) {
4389
+ if (raw.type !== "item.completed")
4390
+ return;
4391
+ const item = raw.item;
4392
+ if (!item)
4393
+ return;
4394
+ if (item.type === "agent_message" && typeof item.text === "string") {
4395
+ yield { type: "text_delta", text: `${item.text}
4396
+ ` };
4397
+ } else if (item.type === "command_execution" && typeof item.command === "string") {
4398
+ yield {
4399
+ type: "tool_use",
4400
+ id: typeof item.id === "string" ? item.id : "",
4401
+ name: "command_execution",
4402
+ input: { command: item.command }
4403
+ };
4404
+ if (typeof item.aggregated_output === "string") {
4405
+ yield {
4406
+ type: "tool_result",
4407
+ toolUseId: typeof item.id === "string" ? item.id : "",
4408
+ content: item.aggregated_output || `(exit code: ${item.exit_code ?? "unknown"})`
4409
+ };
4410
+ }
4411
+ }
4412
+ }
4413
+
4414
+ // lib/codex/streamer.ts
4415
+ async function streamCodexEvents(events, sink, onRawEvent) {
4416
+ let hadTextOutput = false;
4417
+ for await (const raw of events) {
4418
+ onRawEvent?.(raw);
4419
+ for (const event of parseCodexRawEvent(raw)) {
4420
+ processEvent(event, sink, { hadTextOutput });
4421
+ if (event.type === "text_delta") {
4422
+ hadTextOutput = true;
4423
+ } else if (event.type === "tool_use") {
4424
+ hadTextOutput = false;
4425
+ }
4426
+ }
4427
+ }
4428
+ }
4429
+
4430
+ // lib/codex/run.ts
4431
+ var defaultRunnerDependencies2 = {
4432
+ spawnCodex,
4433
+ createStdoutSink,
4434
+ streamCodexEvents
4435
+ };
4436
+ async function run2(prompt, options = {}, dependencies = defaultRunnerDependencies2) {
4437
+ const isRunOptions = (opt) => ("spawnOptions" in opt) || ("onRawEvent" in opt);
4438
+ const spawnOptions = isRunOptions(options) ? options.spawnOptions ?? {} : options;
4439
+ const onRawEvent = isRunOptions(options) ? options.onRawEvent : undefined;
4440
+ const events = dependencies.spawnCodex(prompt, spawnOptions);
4441
+ const sink = dependencies.createStdoutSink();
4442
+ await dependencies.streamCodexEvents(events, sink, onRawEvent);
4443
+ }
4444
+
4445
+ // lib/cli/commands/loop-codex.ts
4446
+ function createCodexDependencies(overrides = {}) {
4447
+ return {
4448
+ ...createDefaultDependencies(),
4449
+ run: run2,
4450
+ ...overrides,
4451
+ agentType: "codex"
4452
+ };
4453
+ }
4454
+ async function loopCodex(dependencies, loopDependencies = createCodexDependencies()) {
4455
+ return loopClaude(dependencies, {
4456
+ ...loopDependencies,
4457
+ agentType: "codex"
4458
+ });
4459
+ }
4460
+
4284
4461
  // lib/cli/commands/new-goal.ts
4285
4462
  function newGoalInstructions(vars) {
4286
4463
  const intro = vars.isClaudeCodeWeb ? "Follow these steps. Use a todo list to track your progress." : "Follow these steps:";
@@ -4611,6 +4788,7 @@ var commandRegistry = {
4611
4788
  "implement task": implementTask,
4612
4789
  "pick task": pickTask,
4613
4790
  "loop claude": loopClaude,
4791
+ "loop codex": loopCodex,
4614
4792
  "pre push": prePush,
4615
4793
  help
4616
4794
  };
@@ -4677,6 +4855,7 @@ function createFileSystem(primitives) {
4677
4855
  mkdir: async (path, options) => {
4678
4856
  await primitives.mkdir(path, options);
4679
4857
  },
4858
+ getFileCreationTime: (path) => primitives.statSync(path).birthtimeMs,
4680
4859
  readdir: (path) => primitives.readdir(path),
4681
4860
  chmod: (path, mode) => primitives.chmod(path, mode)
4682
4861
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {