@joshski/dust 0.1.67 → 0.1.68

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/dust.js CHANGED
@@ -275,7 +275,7 @@ async function loadSettings(cwd, fileSystem) {
275
275
  }
276
276
 
277
277
  // lib/version.ts
278
- var DUST_VERSION = "0.1.67";
278
+ var DUST_VERSION = "0.1.68";
279
279
 
280
280
  // lib/session.ts
281
281
  var DUST_UNATTENDED = "DUST_UNATTENDED";
@@ -609,6 +609,102 @@ function extractOpeningSentence(content) {
609
609
 
610
610
  // lib/audits/stock-audits.ts
611
611
  var ideasHint = "Review existing ideas in `./.dust/ideas/` to understand what has been proposed or considered historically, then create new idea files in `./.dust/ideas/` for any issues you identify, avoiding duplication.";
612
+ function dataAccessReview() {
613
+ return dedent`
614
+ # Data Access Review
615
+
616
+ Review data access patterns for performance issues and optimization opportunities.
617
+
618
+ ${ideasHint}
619
+
620
+ ## Scope
621
+
622
+ Focus on these areas:
623
+
624
+ 1. **N+1 query patterns** - Loops that make individual data requests instead of batching
625
+ 2. **Missing indexes** - Database schema files lacking indexes on frequently queried columns
626
+ 3. **Inefficient data loading** - Over-fetching (loading more data than needed) or under-fetching (requiring multiple round trips)
627
+ 4. **Caching opportunities** - Repeated lookups that could benefit from memoization or caching
628
+ 5. **Batch processing** - Sequential operations that could be parallelized or batched
629
+ 6. **Connection management** - Connection pooling configuration and resource cleanup
630
+
631
+ ## Analysis Steps
632
+
633
+ 1. Search for loops containing data access calls (API requests, database queries, file reads)
634
+ 2. Review database schema or migration files for index definitions
635
+ 3. Identify functions that make multiple related data requests
636
+ 4. Look for repeated identical lookups within the same request lifecycle
637
+ 5. Check for proper resource cleanup (connection closing, stream ending)
638
+
639
+ ## Applicability
640
+
641
+ This audit applies to codebases that interact with:
642
+ - Databases (SQL, NoSQL, ORM queries)
643
+ - External APIs (REST, GraphQL, gRPC)
644
+ - File systems (reading/writing files)
645
+ - Caches (Redis, Memcached, in-memory)
646
+
647
+ If none of these apply, document that finding and skip the detailed analysis.
648
+
649
+ ## Principles
650
+
651
+ - [Decoupled Code](../principles/decoupled-code.md) - Data access should be isolated for testability
652
+ - [Fast Feedback](../principles/fast-feedback.md) - Efficient data access enables faster feedback loops
653
+ - [Maintainable Codebase](../principles/maintainable-codebase.md) - Good data patterns improve maintainability
654
+
655
+ ## Blocked By
656
+
657
+ (none)
658
+
659
+ ## Definition of Done
660
+
661
+ - [ ] Searched for N+1 query patterns (loops with data access)
662
+ - [ ] Reviewed database schemas for missing indexes (if applicable)
663
+ - [ ] Identified over-fetching or under-fetching patterns
664
+ - [ ] Found repeated lookups that could be cached
665
+ - [ ] Checked for sequential operations that could be batched
666
+ - [ ] Verified connection/resource cleanup is handled properly
667
+ - [ ] Proposed ideas for any data access improvements identified
668
+ `;
669
+ }
670
+ function coverageExclusions() {
671
+ return dedent`
672
+ # Coverage Exclusions
673
+
674
+ Review coverage exclusion configuration to identify opportunities for removal through refactoring.
675
+
676
+ ${ideasHint}
677
+
678
+ ## Scope
679
+
680
+ Focus on these areas:
681
+
682
+ 1. **Current exclusions** - Review all exclusions in \`vitest.config.ts\` or equivalent
683
+ 2. **Justification** - Is each exclusion still necessary?
684
+ 3. **Tooling limitations** - Can workarounds be found for coverage tool issues?
685
+ 4. **Decoupling opportunities** - Can excluded code be restructured to enable testing?
686
+ 5. **Entry point patterns** - Can hard-to-test entry points be decoupled from logic?
687
+
688
+ ## Principles
689
+
690
+ - [Decoupled Code](../principles/decoupled-code.md)
691
+ - [Unit Test Coverage](../principles/unit-test-coverage.md)
692
+ - [Comprehensive Test Coverage](../principles/comprehensive-test-coverage.md)
693
+ - [Make Changes with Confidence](../principles/make-changes-with-confidence.md)
694
+
695
+ ## Blocked By
696
+
697
+ (none)
698
+
699
+ ## Definition of Done
700
+
701
+ - [ ] Identified all coverage exclusions in the project
702
+ - [ ] Documented the reason each exclusion exists
703
+ - [ ] Evaluated whether each exclusion is still necessary
704
+ - [ ] Identified exclusions that could be removed through decoupling
705
+ - [ ] Proposed ideas for refactoring where feasible
706
+ `;
707
+ }
612
708
  function componentReuse() {
613
709
  return dedent`
614
710
  # Component Reuse
@@ -819,6 +915,56 @@ function ideasFromPrinciples() {
819
915
  - [ ] Proposed new ideas for unmet or underserved principles
820
916
  `;
821
917
  }
918
+ function refactoringOpportunities() {
919
+ return dedent`
920
+ # Refactoring Opportunities
921
+
922
+ Analyze recent commits to identify code needing structural improvements.
923
+
924
+ ${ideasHint}
925
+
926
+ ## Scope
927
+
928
+ Analyze commits since the last refactoring-opportunities audit (check \`.dust/done/\` for previous runs). Focus on these signals:
929
+
930
+ 1. **File churn** - Files modified frequently across multiple commits may have unclear responsibilities or be accumulating technical debt
931
+ 2. **Size growth** - Files that have grown significantly may benefit from decomposition
932
+ 3. **Commit message patterns** - Look for messages containing "fix", "workaround", "temporary", "hack", or "TODO" that indicate shortcuts taken
933
+
934
+ ## Analysis Steps
935
+
936
+ 1. Run \`git log --since="<last-audit-date>" --name-only --pretty=format:"COMMIT:%s"\` to get commits with their messages and changed files
937
+ 2. Count file modification frequency to identify high-churn files
938
+ 3. Check current sizes of frequently-modified files with \`wc -l\`
939
+ 4. Review commit messages for patterns suggesting technical debt
940
+
941
+ ## Output
942
+
943
+ For each refactoring opportunity identified, provide:
944
+ - **File path** - The specific file needing attention
945
+ - **Signal** - What triggered this recommendation (churn, size, commit pattern)
946
+ - **Specific suggestion** - A concrete refactoring action (e.g., "Extract the validation logic into a separate module", not just "consider refactoring")
947
+
948
+ ## Principles
949
+
950
+ - [Boy Scout Rule](../principles/boy-scout-rule.md) - Leave code better than found, but capture large cleanups as separate tasks
951
+ - [Make the Change Easy](../principles/make-the-change-easy.md) - Refactor until the change becomes straightforward
952
+ - [Make Changes with Confidence](../principles/make-changes-with-confidence.md) - Tests and checks enable safe refactoring
953
+ - [Reasonably DRY](../principles/reasonably-dry.md) - Extract only when duplication represents the same concept
954
+
955
+ ## Blocked By
956
+
957
+ (none)
958
+
959
+ ## Definition of Done
960
+
961
+ - [ ] Identified high-churn files (modified in 3+ commits since last audit)
962
+ - [ ] Flagged files exceeding 300 lines that grew significantly
963
+ - [ ] Noted commits with concerning message patterns
964
+ - [ ] Provided specific refactoring suggestions for each opportunity
965
+ - [ ] Created ideas for any substantial refactoring work identified
966
+ `;
967
+ }
822
968
  function performanceReview() {
823
969
  return dedent`
824
970
  # Performance Review
@@ -959,17 +1105,129 @@ function testCoverage() {
959
1105
  - [ ] Proposed ideas for any test coverage gaps identified
960
1106
  `;
961
1107
  }
1108
+ function errorHandling() {
1109
+ return dedent`
1110
+ # Error Handling
1111
+
1112
+ Review error handling patterns for consistency and agent-friendliness.
1113
+
1114
+ ${ideasHint}
1115
+
1116
+ ## Scope
1117
+
1118
+ Focus on these areas:
1119
+
1120
+ 1. **Silently swallowed errors** - Empty catch blocks, \`.catch(() => {})\`, errors caught but not logged or re-thrown
1121
+ 2. **Missing error context** - Errors converted to booleans or generic messages that lose details
1122
+ 3. **Fire-and-forget promises** - Promises without \`.catch()\` or \`await\` that may fail silently
1123
+ 4. **Non-actionable error messages** - Error messages that say what went wrong but not how to fix it
1124
+ 5. **Inconsistent error recovery** - Similar error scenarios handled differently across the codebase
1125
+
1126
+ ## Analysis Steps
1127
+
1128
+ 1. Search for empty catch blocks: \`catch {}\`, \`catch () {}\`, \`.catch(() => {})\`
1129
+ 2. Look for patterns that discard error details: \`catch { return false }\`, \`catch { return null }\`
1130
+ 3. Find promises without error handling: unassigned or not-awaited promises
1131
+ 4. Review error messages in \`throw\` statements and \`context.stderr()\` calls for actionability
1132
+ 5. Compare error handling patterns across similar operations for consistency
1133
+
1134
+ ## Output
1135
+
1136
+ For each error handling issue identified, provide:
1137
+ - **Location** - File path and line number
1138
+ - **Pattern** - Which category of issue (swallowed, missing context, fire-and-forget, etc.)
1139
+ - **Impact** - What failures could go unnoticed or be hard to debug
1140
+ - **Suggestion** - Specific fix (add logging, propagate error, add recovery guidance)
1141
+
1142
+ ## Principles
1143
+
1144
+ - [Actionable Errors](../principles/actionable-errors.md) - Error messages should tell you what to do next
1145
+ - [Debugging Tooling](../principles/debugging-tooling.md) - Agents need readable, structured error output
1146
+ - [Stop the Line](../principles/stop-the-line.md) - Problems should be fixed at source, not hidden
1147
+
1148
+ ## Blocked By
1149
+
1150
+ (none)
1151
+
1152
+ ## Definition of Done
1153
+
1154
+ - [ ] Searched for empty catch blocks and silent error swallowing
1155
+ - [ ] Identified patterns that discard error details
1156
+ - [ ] Found fire-and-forget promises without error handling
1157
+ - [ ] Reviewed error messages for actionability
1158
+ - [ ] Compared error handling consistency across similar operations
1159
+ - [ ] Proposed ideas for any error handling improvements identified
1160
+ `;
1161
+ }
1162
+ function ubiquitousLanguage() {
1163
+ return dedent`
1164
+ # Ubiquitous Language
1165
+
1166
+ Verify terminology consistency across code, documentation, and user interface.
1167
+
1168
+ ${ideasHint}
1169
+
1170
+ ## Scope
1171
+
1172
+ Focus on these areas:
1173
+
1174
+ 1. **Terminology drift** - Do recent changes introduce terms that deviate from established vocabulary?
1175
+ 2. **Code-to-docs alignment** - Are variables, functions, and types named consistently with documentation?
1176
+ 3. **User interface consistency** - Do UI labels and messages match the terms used in code and docs?
1177
+ 4. **Glossary adherence** - If a glossary exists, is it being followed?
1178
+ 5. **Acronym and abbreviation usage** - Are shortened forms used consistently?
1179
+
1180
+ ## Analysis Steps
1181
+
1182
+ 1. Identify key domain terms from documentation, README, or existing glossary
1183
+ 2. Review recent commits for new terminology or naming choices
1184
+ 3. Compare code identifiers against documented terminology
1185
+ 4. Check user-facing strings for consistency with technical naming
1186
+ 5. Flag deviations where the same concept uses different names
1187
+
1188
+ ## Output
1189
+
1190
+ For each terminology issue identified, provide:
1191
+ - **Term in question** - The inconsistent or unclear term
1192
+ - **Where found** - File paths and locations where the term appears
1193
+ - **Recommended action** - Standardize on existing term, or propose a new canonical name
1194
+
1195
+ ## Principles
1196
+
1197
+ - [Naming Matters](../principles/naming-matters.md) - Good naming reduces waste by eliminating confusion
1198
+ - [Consistent Naming](../principles/consistent-naming.md) - Names should follow established conventions
1199
+ - [Clarity Over Brevity](../principles/clarity-over-brevity.md) - Names should be descriptive and self-documenting
1200
+
1201
+ ## Blocked By
1202
+
1203
+ (none)
1204
+
1205
+ ## Definition of Done
1206
+
1207
+ - [ ] Identified key domain terms from project documentation
1208
+ - [ ] Reviewed recent commits for terminology consistency
1209
+ - [ ] Compared code naming against documentation vocabulary
1210
+ - [ ] Checked user-facing text for alignment with code terms
1211
+ - [ ] Documented any terminology drift or inconsistencies found
1212
+ - [ ] Proposed ideas for standardizing inconsistent terminology
1213
+ `;
1214
+ }
962
1215
  var stockAuditFunctions = {
963
1216
  "agent-developer-experience": agentDeveloperExperience,
964
1217
  "component-reuse": componentReuse,
1218
+ "coverage-exclusions": coverageExclusions,
1219
+ "data-access-review": dataAccessReview,
965
1220
  "dead-code": deadCode,
1221
+ "error-handling": errorHandling,
966
1222
  "facts-verification": factsVerification,
967
1223
  "ideas-from-commits": ideasFromCommits,
968
1224
  "ideas-from-principles": ideasFromPrinciples,
969
1225
  "performance-review": performanceReview,
1226
+ "refactoring-opportunities": refactoringOpportunities,
970
1227
  "security-review": securityReview,
971
1228
  "stale-ideas": staleIdeas,
972
- "test-coverage": testCoverage
1229
+ "test-coverage": testCoverage,
1230
+ "ubiquitous-language": ubiquitousLanguage
973
1231
  };
974
1232
  function loadStockAudits() {
975
1233
  return Object.entries(stockAuditFunctions).sort(([a], [b]) => a.localeCompare(b)).map(([name, render]) => {
@@ -988,6 +1246,24 @@ function transformAuditContent(content) {
988
1246
  const originalTitle = titleMatch[1];
989
1247
  return content.replace(/^#\s+.+$/m, `# Audit: ${originalTitle}`);
990
1248
  }
1249
+ function injectAdHocScope(content, adHocDetails) {
1250
+ const scopeMatch = content.match(/\n## Scope\n/);
1251
+ if (scopeMatch?.index !== undefined) {
1252
+ const insertIndex = scopeMatch.index;
1253
+ const adHocSection = `
1254
+ ## Ad-hoc Scope
1255
+
1256
+ ${adHocDetails}
1257
+ `;
1258
+ return content.slice(0, insertIndex) + adHocSection + content.slice(insertIndex);
1259
+ }
1260
+ return `${content}
1261
+
1262
+ ## Ad-hoc Scope
1263
+
1264
+ ${adHocDetails}
1265
+ `;
1266
+ }
991
1267
 
992
1268
  // lib/cli/colors.ts
993
1269
  var ANSI_COLORS = {
@@ -1023,7 +1299,7 @@ function getColors() {
1023
1299
  }
1024
1300
 
1025
1301
  // lib/cli/commands/audit.ts
1026
- async function addAudit(auditName, dependencies) {
1302
+ async function addAudit(auditName, adHocDetails, dependencies) {
1027
1303
  const { context, fileSystem, settings } = dependencies;
1028
1304
  const dustPath = `${context.cwd}/.dust`;
1029
1305
  const userAuditsPath = `${dustPath}/config/audits`;
@@ -1037,7 +1313,10 @@ async function addAudit(auditName, dependencies) {
1037
1313
  const userAuditPath = `${userAuditsPath}/${auditName}.md`;
1038
1314
  if (fileSystem.exists(userAuditPath)) {
1039
1315
  const content = await fileSystem.readFile(userAuditPath);
1040
- const transformedContent = transformAuditContent(content);
1316
+ let transformedContent = transformAuditContent(content);
1317
+ if (adHocDetails) {
1318
+ transformedContent = injectAdHocScope(transformedContent, adHocDetails);
1319
+ }
1041
1320
  await fileSystem.mkdir(tasksPath, { recursive: true });
1042
1321
  await fileSystem.writeFile(taskFilePath, transformedContent);
1043
1322
  context.stdout(`→ ${relativeTaskPath}`);
@@ -1045,7 +1324,10 @@ async function addAudit(auditName, dependencies) {
1045
1324
  }
1046
1325
  const stockAudit = loadStockAudits().find((a) => a.name === auditName);
1047
1326
  if (stockAudit) {
1048
- const transformedContent = transformAuditContent(stockAudit.template);
1327
+ let transformedContent = transformAuditContent(stockAudit.template);
1328
+ if (adHocDetails) {
1329
+ transformedContent = injectAdHocScope(transformedContent, adHocDetails);
1330
+ }
1049
1331
  await fileSystem.mkdir(tasksPath, { recursive: true });
1050
1332
  await fileSystem.writeFile(taskFilePath, transformedContent);
1051
1333
  context.stdout(`→ ${relativeTaskPath}`);
@@ -1101,8 +1383,9 @@ async function listAudits(dependencies) {
1101
1383
  }
1102
1384
  async function audit(dependencies) {
1103
1385
  const auditName = dependencies.arguments[0];
1386
+ const adHocDetails = dependencies.arguments[1];
1104
1387
  if (auditName) {
1105
- return addAudit(auditName, dependencies);
1388
+ return addAudit(auditName, adHocDetails, dependencies);
1106
1389
  }
1107
1390
  return listAudits(dependencies);
1108
1391
  }
@@ -2689,6 +2972,51 @@ async function handleRepositoryList(repositories, manager, repoDeps, context) {
2689
2972
  }
2690
2973
  }
2691
2974
 
2975
+ // lib/bucket/server-messages.ts
2976
+ function parseServerMessage(data) {
2977
+ if (typeof data !== "object" || data === null) {
2978
+ return null;
2979
+ }
2980
+ const message = data;
2981
+ if (message.type === "repository-list") {
2982
+ if (!Array.isArray(message.repositories)) {
2983
+ return null;
2984
+ }
2985
+ const repositories = [];
2986
+ for (const r of message.repositories) {
2987
+ if (typeof r !== "object" || r === null) {
2988
+ return null;
2989
+ }
2990
+ const repo = r;
2991
+ if (typeof repo.name !== "string" || typeof repo.gitUrl !== "string") {
2992
+ return null;
2993
+ }
2994
+ const item = {
2995
+ name: repo.name,
2996
+ gitUrl: repo.gitUrl
2997
+ };
2998
+ if (typeof repo.url === "string") {
2999
+ item.url = repo.url;
3000
+ }
3001
+ if (typeof repo.id === "string") {
3002
+ item.id = repo.id;
3003
+ }
3004
+ if (typeof repo.hasTask === "boolean") {
3005
+ item.hasTask = repo.hasTask;
3006
+ }
3007
+ repositories.push(item);
3008
+ }
3009
+ return { type: "repository-list", repositories };
3010
+ }
3011
+ if (message.type === "task-available") {
3012
+ if (typeof message.repository !== "string") {
3013
+ return null;
3014
+ }
3015
+ return { type: "task-available", repository: message.repository };
3016
+ }
3017
+ return null;
3018
+ }
3019
+
2692
3020
  // lib/bucket/terminal-ui.ts
2693
3021
  var ANSI = {
2694
3022
  HIDE_CURSOR: "\x1B[?25l",
@@ -3275,20 +3603,17 @@ function signalTaskAvailable(repoState, state, repoDeps, context, useTUI) {
3275
3603
  }
3276
3604
  function syncUIWithRepoList(state, repos) {
3277
3605
  const incomingNames = new Set;
3278
- for (const data of repos) {
3279
- const repo = parseRepository(data);
3280
- if (repo) {
3281
- incomingNames.add(repo.name);
3282
- if (!state.ui.repositories.includes(repo.name)) {
3283
- let buffer = state.logBuffers.get(repo.name);
3284
- if (!buffer) {
3285
- buffer = createLogBuffer();
3286
- state.logBuffers.set(repo.name, buffer);
3287
- }
3288
- addRepository2(state.ui, repo.name, buffer, repo.url);
3289
- } else if (repo.url) {
3290
- state.ui.repositoryUrls.set(repo.name, repo.url);
3606
+ for (const repo of repos) {
3607
+ incomingNames.add(repo.name);
3608
+ if (!state.ui.repositories.includes(repo.name)) {
3609
+ let buffer = state.logBuffers.get(repo.name);
3610
+ if (!buffer) {
3611
+ buffer = createLogBuffer();
3612
+ state.logBuffers.set(repo.name, buffer);
3291
3613
  }
3614
+ addRepository2(state.ui, repo.name, buffer, repo.url);
3615
+ } else if (repo.url) {
3616
+ state.ui.repositoryUrls.set(repo.name, repo.url);
3292
3617
  }
3293
3618
  }
3294
3619
  for (const name of [...state.ui.repositories]) {
@@ -3394,47 +3719,67 @@ function connectWebSocket(token, state, bucketDependencies, context, fileSystem,
3394
3719
  logMessage(state, context, useTUI, `WebSocket error: ${error.message}`, "stderr");
3395
3720
  };
3396
3721
  ws.onmessage = (event) => {
3722
+ let rawData;
3397
3723
  try {
3398
- const message = JSON.parse(event.data);
3399
- log4(`ws message: ${message.type}`);
3400
- if (message.type === "repository-list") {
3401
- const repos = message.repositories ?? [];
3402
- const repoNames = repos.map((r) => {
3403
- const name = r?.name ?? "?";
3404
- return r?.hasTask ? `${name} (has task)` : name;
3405
- }).join(", ");
3406
- logMessage(state, context, useTUI, `Received repository list: ${repoNames || "(empty)"}`);
3407
- syncUIWithRepoList(state, repos);
3408
- const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
3409
- const repoContext = createTUIContext(state, context, useTUI);
3410
- handleRepositoryList(repos, state, repoDeps, repoContext).then(() => {
3411
- syncTUI(state);
3412
- for (const repoData of repos) {
3413
- if (typeof repoData === "object" && repoData !== null && "name" in repoData && "hasTask" in repoData && repoData.hasTask) {
3414
- const repoState = state.repositories.get(repoData.name);
3415
- if (repoState) {
3416
- signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
3417
- }
3418
- }
3724
+ rawData = JSON.parse(event.data);
3725
+ } catch {
3726
+ logMessage(state, context, useTUI, `Failed to parse WebSocket message: ${event.data}`, "stderr");
3727
+ return;
3728
+ }
3729
+ const message = parseServerMessage(rawData);
3730
+ if (!message) {
3731
+ logMessage(state, context, useTUI, `Invalid WebSocket message format: ${event.data}`, "stderr");
3732
+ return;
3733
+ }
3734
+ log4(`ws message: ${message.type}`);
3735
+ if (message.type === "repository-list") {
3736
+ const repos = message.repositories;
3737
+ logMessage(state, context, useTUI, `Received repository list (${repos.length} repositories):`);
3738
+ if (repos.length === 0) {
3739
+ logMessage(state, context, useTUI, " (empty)");
3740
+ } else {
3741
+ for (const r of repos) {
3742
+ const attrs = [];
3743
+ attrs.push(`name=${r.name}`);
3744
+ if (r.id !== undefined) {
3745
+ attrs.push(`id=${r.id}`);
3419
3746
  }
3420
- }).catch((error) => {
3421
- logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
3422
- });
3423
- } else if (message.type === "task-available") {
3424
- const repoName = message.repository;
3425
- if (typeof repoName === "string") {
3426
- const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
3427
- logMessage(state, context, useTUI, `Received task-available for ${repoName}`);
3428
- const repoState = state.repositories.get(repoName);
3429
- if (repoState) {
3430
- signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
3431
- } else {
3432
- logMessage(state, context, useTUI, `No repository state found for ${repoName}`, "stderr");
3747
+ attrs.push(`gitUrl=${r.gitUrl}`);
3748
+ if (r.url !== undefined) {
3749
+ attrs.push(`url=${r.url}`);
3750
+ }
3751
+ if (r.hasTask !== undefined) {
3752
+ attrs.push(`hasTask=${r.hasTask}`);
3433
3753
  }
3754
+ logMessage(state, context, useTUI, ` - ${attrs.join(", ")}`);
3434
3755
  }
3435
3756
  }
3436
- } catch {
3437
- logMessage(state, context, useTUI, `Failed to parse WebSocket message: ${event.data}`, "stderr");
3757
+ syncUIWithRepoList(state, repos);
3758
+ const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
3759
+ const repoContext = createTUIContext(state, context, useTUI);
3760
+ handleRepositoryList(repos, state, repoDeps, repoContext).then(() => {
3761
+ syncTUI(state);
3762
+ for (const repoData of repos) {
3763
+ if (repoData.hasTask) {
3764
+ const repoState = state.repositories.get(repoData.name);
3765
+ if (repoState) {
3766
+ signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
3767
+ }
3768
+ }
3769
+ }
3770
+ }).catch((error) => {
3771
+ logMessage(state, context, useTUI, `Failed to handle repository list: ${error.message}`, "stderr");
3772
+ });
3773
+ } else if (message.type === "task-available") {
3774
+ const repoName = message.repository;
3775
+ const repoDeps = toRepositoryDependencies(bucketDependencies, fileSystem);
3776
+ logMessage(state, context, useTUI, `Received task-available for ${repoName}`);
3777
+ const repoState = state.repositories.get(repoName);
3778
+ if (repoState) {
3779
+ signalTaskAvailable(repoState, state, repoDeps, context, useTUI);
3780
+ } else {
3781
+ logMessage(state, context, useTUI, `No repository state found for ${repoName}`, "stderr");
3782
+ }
3438
3783
  }
3439
3784
  };
3440
3785
  }
@@ -4840,6 +5185,7 @@ function generateHelpText(settings) {
4840
5185
  next Show tasks ready to work on (not blocked)
4841
5186
  check Run project-defined quality gate hook
4842
5187
  agent Agent greeting and routing instructions
5188
+ audit Create tasks from audit templates
4843
5189
  focus Declare current objective (for remote session tracking)
4844
5190
  pick task Pick the next task to work on
4845
5191
  implement task Implement a task
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Git hooks management for dust
3
+ *
4
+ * Handles installation and verification of git pre-push hooks
5
+ * that run `dust pre push` automatically before code leaves the machine.
6
+ */
7
+ import type { DustSettings } from '../cli/types';
8
+ import type { FileSystem } from '../filesystem/types';
9
+ interface HooksManager {
10
+ isGitRepo: () => boolean;
11
+ isHookInstalled: () => Promise<boolean>;
12
+ installHook: () => Promise<void>;
13
+ getHookBinaryPath: () => Promise<string | null>;
14
+ updateHookBinaryPath: (newPath: string) => Promise<void>;
15
+ }
16
+ export declare function createHooksManager(cwd: string, fileSystem: FileSystem, settings: DustSettings): HooksManager;
17
+ export {};
@@ -0,0 +1,7 @@
1
+ export declare const DUST_UNATTENDED = "DUST_UNATTENDED";
2
+ export declare const DUST_SKIP_AGENT = "DUST_SKIP_AGENT";
3
+ export declare const DUST_REPOSITORY_ID = "DUST_REPOSITORY_ID";
4
+ export declare function isUnattended(env?: Record<string, string | undefined>): boolean;
5
+ export declare function buildUnattendedEnv(options?: {
6
+ repositoryId?: string;
7
+ }): Record<string, string>;
package/dist/types.d.ts CHANGED
@@ -7,3 +7,4 @@
7
7
  export type { AgentSessionEvent, EventMessage } from './agent-events';
8
8
  export type { Idea, IdeaOpenQuestion, IdeaOption } from './artifacts/ideas';
9
9
  export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, WorkflowTaskType, } from './artifacts/workflow-tasks';
10
+ export type { Repository } from './bucket/repository';
@@ -0,0 +1 @@
1
+ export declare const DUST_VERSION: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -65,6 +65,8 @@
65
65
  "@vitest/coverage-v8": "^4.0.18",
66
66
  "istanbul-lib-coverage": "^3.2.2",
67
67
  "istanbul-lib-report": "^3.0.1",
68
+ "knip": "^5.60.1",
69
+ "typescript": "^5.8.3",
68
70
  "vitest": "^4.0.18"
69
71
  }
70
72
  }