@joshski/dust 0.1.56 → 0.1.57

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
@@ -1177,7 +1177,7 @@ function getLogLines(buffer) {
1177
1177
  }
1178
1178
 
1179
1179
  // lib/bucket/repository.ts
1180
- import { dirname as dirname2, join as join8 } from "node:path";
1180
+ import { dirname as dirname3, join as join8 } from "node:path";
1181
1181
 
1182
1182
  // lib/claude/spawn-claude-code.ts
1183
1183
  import { spawn as nodeSpawn } from "node:child_process";
@@ -1595,6 +1595,9 @@ async function run(prompt, options = {}, dependencies = defaultRunnerDependencie
1595
1595
  await dependencies.streamEvents(events, sink, onRawEvent);
1596
1596
  }
1597
1597
 
1598
+ // lib/logging/index.ts
1599
+ import { join as join5 } from "node:path";
1600
+
1598
1601
  // lib/logging/match.ts
1599
1602
  function parsePatterns(debug) {
1600
1603
  if (!debug)
@@ -1617,41 +1620,47 @@ function formatLine(name, messages) {
1617
1620
 
1618
1621
  // lib/logging/sink.ts
1619
1622
  import { appendFileSync, mkdirSync } from "node:fs";
1620
- import { join as join5 } from "node:path";
1621
- var logPath;
1622
- var ready = false;
1623
- var scope = process.env.DEBUG_LOG_SCOPE || "debug";
1624
- function ensureLogFile() {
1625
- if (ready)
1626
- return logPath;
1627
- ready = true;
1628
- const dir = join5(process.cwd(), "log", "dust");
1629
- logPath = join5(dir, `${scope}.log`);
1630
- try {
1631
- mkdirSync(dir, { recursive: true });
1632
- } catch {
1633
- logPath = undefined;
1634
- }
1635
- return logPath;
1636
- }
1637
- function setLogScope(name) {
1638
- scope = name;
1639
- process.env.DEBUG_LOG_SCOPE = name;
1640
- logPath = undefined;
1623
+ import { dirname } from "node:path";
1624
+
1625
+ class FileSink {
1626
+ logPath;
1627
+ _appendFileSync;
1628
+ _mkdirSync;
1629
+ resolvedPath;
1641
1630
  ready = false;
1631
+ constructor(logPath, _appendFileSync = appendFileSync, _mkdirSync = mkdirSync) {
1632
+ this.logPath = logPath;
1633
+ this._appendFileSync = _appendFileSync;
1634
+ this._mkdirSync = _mkdirSync;
1635
+ }
1636
+ ensureLogFile() {
1637
+ if (this.ready)
1638
+ return this.resolvedPath;
1639
+ this.ready = true;
1640
+ this.resolvedPath = this.logPath;
1641
+ try {
1642
+ this._mkdirSync(dirname(this.logPath), { recursive: true });
1643
+ } catch {
1644
+ this.resolvedPath = undefined;
1645
+ }
1646
+ return this.resolvedPath;
1647
+ }
1648
+ write(line) {
1649
+ const path = this.ensureLogFile();
1650
+ if (!path)
1651
+ return;
1652
+ try {
1653
+ this._appendFileSync(path, line);
1654
+ } catch {}
1655
+ }
1642
1656
  }
1643
- var writeToFile = (line) => {
1644
- const path = ensureLogFile();
1645
- if (!path)
1646
- return;
1647
- try {
1648
- appendFileSync(path, line);
1649
- } catch {}
1650
- };
1651
1657
 
1652
1658
  // lib/logging/index.ts
1659
+ var DUST_LOG_FILE = "DUST_LOG_FILE";
1653
1660
  var patterns = null;
1654
1661
  var initialized = false;
1662
+ var activeFileSink = null;
1663
+ var ownedDustLogFile = false;
1655
1664
  function init() {
1656
1665
  if (initialized)
1657
1666
  return;
@@ -1659,12 +1668,26 @@ function init() {
1659
1668
  const parsed = parsePatterns(process.env.DEBUG);
1660
1669
  patterns = parsed.length > 0 ? parsed : null;
1661
1670
  }
1662
- function createLogger(name, write = writeToFile) {
1671
+ function enableFileLogs(scope, _sinkForTesting) {
1672
+ const existing = process.env[DUST_LOG_FILE];
1673
+ const logDir = process.env.DUST_LOG_DIR ?? join5(process.cwd(), "log");
1674
+ const path = existing ?? join5(logDir, `${scope}.log`);
1675
+ if (!existing) {
1676
+ process.env[DUST_LOG_FILE] = path;
1677
+ ownedDustLogFile = true;
1678
+ }
1679
+ activeFileSink = _sinkForTesting ?? new FileSink(path);
1680
+ }
1681
+ function createLogger(name) {
1663
1682
  return (...messages) => {
1664
1683
  init();
1665
- if (!patterns || !matchesAny(name, patterns))
1666
- return;
1667
- write(formatLine(name, messages));
1684
+ const line = formatLine(name, messages);
1685
+ if (activeFileSink) {
1686
+ activeFileSink.write(line);
1687
+ }
1688
+ if (patterns && matchesAny(name, patterns)) {
1689
+ process.stdout.write(line);
1690
+ }
1668
1691
  };
1669
1692
  }
1670
1693
 
@@ -1742,7 +1765,7 @@ function formatAgentEvent(event) {
1742
1765
  import { spawn as nodeSpawn2 } from "node:child_process";
1743
1766
  import { readFileSync } from "node:fs";
1744
1767
  import os from "node:os";
1745
- import { dirname, join as join7 } from "node:path";
1768
+ import { dirname as dirname2, join as join7 } from "node:path";
1746
1769
  import { fileURLToPath } from "node:url";
1747
1770
 
1748
1771
  // lib/workflow-tasks.ts
@@ -1887,7 +1910,7 @@ async function next(dependencies) {
1887
1910
  }
1888
1911
 
1889
1912
  // lib/cli/commands/loop.ts
1890
- var __dirname2 = dirname(fileURLToPath(import.meta.url));
1913
+ var __dirname2 = dirname2(fileURLToPath(import.meta.url));
1891
1914
  function getDustVersion() {
1892
1915
  const candidates = [
1893
1916
  join7(__dirname2, "../../../package.json"),
@@ -2137,6 +2160,7 @@ function parseMaxIterations(commandArguments) {
2137
2160
  return parsed;
2138
2161
  }
2139
2162
  async function loopClaude(dependencies, loopDependencies = createDefaultDependencies()) {
2163
+ enableFileLogs("loop");
2140
2164
  const { context, settings } = dependencies;
2141
2165
  const { postEvent } = loopDependencies;
2142
2166
  const maxIterations = parseMaxIterations(dependencies.arguments);
@@ -2391,7 +2415,7 @@ async function addRepository(repository, manager, repoDeps, context) {
2391
2415
  }
2392
2416
  log3(`adding repository ${repository.name}`);
2393
2417
  const repoPath = getRepoPath(repository.name, repoDeps.getReposDir());
2394
- await repoDeps.fileSystem.mkdir(dirname2(repoPath), { recursive: true });
2418
+ await repoDeps.fileSystem.mkdir(dirname3(repoPath), { recursive: true });
2395
2419
  if (repoDeps.fileSystem.exists(repoPath)) {
2396
2420
  await removeRepository(repoPath, repoDeps.spawn, context);
2397
2421
  }
@@ -3312,6 +3336,7 @@ async function resolveToken(authDeps, context) {
3312
3336
  }
3313
3337
  }
3314
3338
  async function bucket(dependencies, bucketDeps = createDefaultBucketDependencies()) {
3339
+ enableFileLogs("bucket");
3315
3340
  const { context, fileSystem } = dependencies;
3316
3341
  const token = await resolveToken(bucketDeps.auth, context);
3317
3342
  if (!token) {
@@ -3602,7 +3627,7 @@ function validateTitleFilenameMatch(filePath, content) {
3602
3627
  }
3603
3628
 
3604
3629
  // lib/lint/validators/goal-hierarchy.ts
3605
- import { dirname as dirname3, resolve } from "node:path";
3630
+ import { dirname as dirname4, resolve } from "node:path";
3606
3631
  var REQUIRED_GOAL_HEADINGS = ["## Parent Goal", "## Sub-Goals"];
3607
3632
  function validateGoalHierarchySections(filePath, content) {
3608
3633
  const violations = [];
@@ -3619,7 +3644,7 @@ function validateGoalHierarchySections(filePath, content) {
3619
3644
  function extractGoalRelationships(filePath, content) {
3620
3645
  const lines = content.split(`
3621
3646
  `);
3622
- const fileDir = dirname3(filePath);
3647
+ const fileDir = dirname4(filePath);
3623
3648
  const parentGoals = [];
3624
3649
  const subGoals = [];
3625
3650
  let currentSection = null;
@@ -3828,7 +3853,7 @@ function validateIdeaTransitionTitle(filePath, content, ideasPath, fileSystem) {
3828
3853
  }
3829
3854
 
3830
3855
  // lib/lint/validators/link-validator.ts
3831
- import { dirname as dirname4, resolve as resolve2 } from "node:path";
3856
+ import { dirname as dirname5, resolve as resolve2 } from "node:path";
3832
3857
  var SEMANTIC_RULES = [
3833
3858
  {
3834
3859
  section: "## Goals",
@@ -3845,7 +3870,7 @@ function validateLinks(filePath, content, fileSystem) {
3845
3870
  const violations = [];
3846
3871
  const lines = content.split(`
3847
3872
  `);
3848
- const fileDir = dirname4(filePath);
3873
+ const fileDir = dirname5(filePath);
3849
3874
  for (let i = 0;i < lines.length; i++) {
3850
3875
  const line = lines[i];
3851
3876
  const linkPattern = new RegExp(MARKDOWN_LINK_PATTERN.source, "g");
@@ -3872,7 +3897,7 @@ function validateSemanticLinks(filePath, content) {
3872
3897
  const violations = [];
3873
3898
  const lines = content.split(`
3874
3899
  `);
3875
- const fileDir = dirname4(filePath);
3900
+ const fileDir = dirname5(filePath);
3876
3901
  let currentSection = null;
3877
3902
  for (let i = 0;i < lines.length; i++) {
3878
3903
  const line = lines[i];
@@ -3923,7 +3948,7 @@ function validateGoalHierarchyLinks(filePath, content) {
3923
3948
  const violations = [];
3924
3949
  const lines = content.split(`
3925
3950
  `);
3926
- const fileDir = dirname4(filePath);
3951
+ const fileDir = dirname5(filePath);
3927
3952
  let currentSection = null;
3928
3953
  for (let i = 0;i < lines.length; i++) {
3929
3954
  const line = lines[i];
@@ -4162,6 +4187,24 @@ async function lintMarkdown(dependencies) {
4162
4187
  // lib/cli/commands/check.ts
4163
4188
  var log5 = createLogger("dust.cli.commands.check");
4164
4189
  var DEFAULT_CHECK_TIMEOUT_MS = 13000;
4190
+ var MAX_OUTPUT_LINES = 500;
4191
+ var KEEP_LINES = 250;
4192
+ function truncateOutput(output) {
4193
+ const lines = output.split(`
4194
+ `);
4195
+ if (lines.length <= MAX_OUTPUT_LINES) {
4196
+ return output;
4197
+ }
4198
+ const snippedCount = lines.length - KEEP_LINES * 2;
4199
+ const firstLines = lines.slice(0, KEEP_LINES);
4200
+ const lastLines = lines.slice(-KEEP_LINES);
4201
+ return [
4202
+ ...firstLines,
4203
+ `[...snip ${snippedCount} lines...]`,
4204
+ ...lastLines
4205
+ ].join(`
4206
+ `);
4207
+ }
4165
4208
  async function runSingleCheck(check, cwd, runner) {
4166
4209
  const timeoutMs = check.timeoutMilliseconds ?? DEFAULT_CHECK_TIMEOUT_MS;
4167
4210
  log5(`running check ${check.name}: ${check.command}`);
@@ -4241,7 +4284,7 @@ function displayResults(results, context) {
4241
4284
  context.stdout(`Note: This check was killed after ${result.timeoutSeconds}s. To configure a different timeout, set "timeoutMilliseconds" in the check configuration in .dust/config/settings.json`);
4242
4285
  }
4243
4286
  if (result.output.trim()) {
4244
- context.stdout(result.output.trimEnd());
4287
+ context.stdout(truncateOutput(result.output).trimEnd());
4245
4288
  }
4246
4289
  if (result.hints && result.hints.length > 0) {
4247
4290
  context.stdout("");
@@ -4264,7 +4307,7 @@ async function check(dependencies, shellRunner = defaultShellRunner) {
4264
4307
  settings
4265
4308
  } = dependencies;
4266
4309
  const serial = commandArguments.includes("--serial");
4267
- setLogScope("check");
4310
+ enableFileLogs("check");
4268
4311
  if (!settings.checks || settings.checks.length === 0) {
4269
4312
  context.stderr("Error: No checks configured in .dust/config/settings.json");
4270
4313
  context.stderr("");
@@ -1,12 +1,19 @@
1
1
  /**
2
2
  * Minimal debug logging framework.
3
3
  *
4
- * When the DEBUG environment variable is set, matching loggers write
5
- * timestamped lines to `<cwd>/log/dust/<scope>.log`.
4
+ * Two independent output channels:
6
5
  *
7
- * The scope defaults to "debug" but can be changed via setLogScope()
8
- * so that different commands (e.g. `loop`, `check`, `bucket`) write
9
- * to separate log files.
6
+ * - **File logging** activated by `enableFileLogs(scope)` at command startup.
7
+ * Writes all logs to `./log/<scope>.log` by default. Two env vars control routing:
8
+ *
9
+ * Routing rules for enableFileLogs(scope):
10
+ * 1. If DUST_LOG_FILE is already set (inherited from a parent process such as
11
+ * `dust check`), use that path — all scopes land in the same file.
12
+ * 2. Otherwise compute the path from DUST_LOG_DIR (if set) or `<cwd>/log`, set
13
+ * DUST_LOG_FILE so child processes inherit the same destination, then write there.
14
+ *
15
+ * - **Stdout logging** — activated by `DEBUG=pattern`. Writes matching logs to
16
+ * stdout. Works in any command, regardless of whether file logging is enabled.
10
17
  *
11
18
  * DEBUG is a comma-separated list of match expressions. Each expression
12
19
  * can contain `*` as a wildcard (matches any sequence of characters).
@@ -23,22 +30,32 @@
23
30
  *
24
31
  * No external dependencies.
25
32
  */
26
- import { type WriteFn } from './sink';
27
- export { setLogScope } from './sink';
33
+ import { type LogSink } from './sink';
28
34
  export type LogFn = (...messages: unknown[]) => void;
29
35
  /**
30
- * Create a named logger function. The returned function writes to
31
- * `log/dust/<scope>.log` when the logger name matches the DEBUG patterns.
36
+ * Activate file logging for this command. Determines the log path as follows:
37
+ * - If DUST_LOG_FILE is already set (inherited from a parent process such as
38
+ * `dust check`), use that path — all scopes land in the same file.
39
+ * - Otherwise compute the path using DUST_LOG_DIR (if set) or `<cwd>/log`, set
40
+ * DUST_LOG_FILE so that any child processes inherit the same destination, then write there.
41
+ *
42
+ * Pass a LogSink as the second argument to override for testing.
43
+ */
44
+ export declare function enableFileLogs(scope: string, _sinkForTesting?: LogSink): void;
45
+ /**
46
+ * Create a named logger function. The returned function writes to:
47
+ * - The active file sink (if `enableFileLogs` was called), always, no filtering.
48
+ * - `process.stdout` if DEBUG is set and `name` matches the pattern.
32
49
  *
33
50
  * @param name - Logger name, e.g. `dust.bucket.loop`
34
- * @param write - Override the default file writer (for testing)
35
51
  */
36
- export declare function createLogger(name: string, write?: WriteFn): LogFn;
52
+ export declare function createLogger(name: string): LogFn;
37
53
  /**
38
- * Check whether a logger name would be enabled under the current DEBUG value.
54
+ * Check whether a logger name would produce stdout output under the current DEBUG value.
39
55
  */
40
56
  export declare function isEnabled(name: string): boolean;
41
57
  /**
42
58
  * Reset internal state (for testing only).
59
+ * Clears DUST_LOG_FILE only if this module set it (not if it was inherited).
43
60
  */
44
61
  export declare function _reset(): void;
@@ -1,24 +1,29 @@
1
1
  /**
2
2
  * File-based log sink — the imperative shell for debug logging.
3
3
  *
4
- * Lazily creates `<cwd>/log/dust/<scope>.log` and appends lines to it.
5
- * The scope defaults to "debug" but can be changed via setLogScope()
6
- * so that different commands write to separate log files.
4
+ * Writes log lines to an arbitrary file path, creating the directory lazily
5
+ * on first write. The path is determined by the caller (enableFileLogs in
6
+ * index.ts) rather than this class.
7
7
  */
8
- export type WriteFn = (line: string) => void;
9
- /**
10
- * Set the log scope, which determines the output filename.
11
- * Must be called before any logger writes (i.e. at command startup).
12
- *
13
- * For example, `setLogScope('loop')` writes to `log/dust/loop.log`.
14
- */
15
- export declare function setLogScope(name: string): void;
16
- /**
17
- * Write a line to the debug log file.
18
- * Silently no-ops if the file cannot be opened.
19
- */
20
- export declare const writeToFile: WriteFn;
21
- /**
22
- * Reset sink state (for testing only).
23
- */
24
- export declare function _resetSink(): void;
8
+ export interface LogSink {
9
+ write(line: string): void;
10
+ }
11
+ type AppendFileSyncFn = (path: string, data: string) => void;
12
+ type MkdirSyncFn = (path: string, options: {
13
+ recursive: boolean;
14
+ }) => void;
15
+ export declare class FileSink implements LogSink {
16
+ private readonly logPath;
17
+ private readonly _appendFileSync;
18
+ private readonly _mkdirSync;
19
+ private resolvedPath;
20
+ private ready;
21
+ constructor(logPath: string, _appendFileSync?: AppendFileSyncFn, _mkdirSync?: MkdirSyncFn);
22
+ private ensureLogFile;
23
+ /**
24
+ * Write a line to the log file.
25
+ * Silently no-ops if the file cannot be opened.
26
+ */
27
+ write(line: string): void;
28
+ }
29
+ export {};
package/dist/logging.js CHANGED
@@ -1,3 +1,6 @@
1
+ // lib/logging/index.ts
2
+ import { join } from "node:path";
3
+
1
4
  // lib/logging/match.ts
2
5
  function parsePatterns(debug) {
3
6
  if (!debug)
@@ -20,41 +23,47 @@ function formatLine(name, messages) {
20
23
 
21
24
  // lib/logging/sink.ts
22
25
  import { appendFileSync, mkdirSync } from "node:fs";
23
- import { join } from "node:path";
24
- var logPath;
25
- var ready = false;
26
- var scope = process.env.DEBUG_LOG_SCOPE || "debug";
27
- function ensureLogFile() {
28
- if (ready)
29
- return logPath;
30
- ready = true;
31
- const dir = join(process.cwd(), "log", "dust");
32
- logPath = join(dir, `${scope}.log`);
33
- try {
34
- mkdirSync(dir, { recursive: true });
35
- } catch {
36
- logPath = undefined;
37
- }
38
- return logPath;
39
- }
40
- function setLogScope(name) {
41
- scope = name;
42
- process.env.DEBUG_LOG_SCOPE = name;
43
- logPath = undefined;
26
+ import { dirname } from "node:path";
27
+
28
+ class FileSink {
29
+ logPath;
30
+ _appendFileSync;
31
+ _mkdirSync;
32
+ resolvedPath;
44
33
  ready = false;
34
+ constructor(logPath, _appendFileSync = appendFileSync, _mkdirSync = mkdirSync) {
35
+ this.logPath = logPath;
36
+ this._appendFileSync = _appendFileSync;
37
+ this._mkdirSync = _mkdirSync;
38
+ }
39
+ ensureLogFile() {
40
+ if (this.ready)
41
+ return this.resolvedPath;
42
+ this.ready = true;
43
+ this.resolvedPath = this.logPath;
44
+ try {
45
+ this._mkdirSync(dirname(this.logPath), { recursive: true });
46
+ } catch {
47
+ this.resolvedPath = undefined;
48
+ }
49
+ return this.resolvedPath;
50
+ }
51
+ write(line) {
52
+ const path = this.ensureLogFile();
53
+ if (!path)
54
+ return;
55
+ try {
56
+ this._appendFileSync(path, line);
57
+ } catch {}
58
+ }
45
59
  }
46
- var writeToFile = (line) => {
47
- const path = ensureLogFile();
48
- if (!path)
49
- return;
50
- try {
51
- appendFileSync(path, line);
52
- } catch {}
53
- };
54
60
 
55
61
  // lib/logging/index.ts
62
+ var DUST_LOG_FILE = "DUST_LOG_FILE";
56
63
  var patterns = null;
57
64
  var initialized = false;
65
+ var activeFileSink = null;
66
+ var ownedDustLogFile = false;
58
67
  function init() {
59
68
  if (initialized)
60
69
  return;
@@ -62,12 +71,26 @@ function init() {
62
71
  const parsed = parsePatterns(process.env.DEBUG);
63
72
  patterns = parsed.length > 0 ? parsed : null;
64
73
  }
65
- function createLogger(name, write = writeToFile) {
74
+ function enableFileLogs(scope, _sinkForTesting) {
75
+ const existing = process.env[DUST_LOG_FILE];
76
+ const logDir = process.env.DUST_LOG_DIR ?? join(process.cwd(), "log");
77
+ const path = existing ?? join(logDir, `${scope}.log`);
78
+ if (!existing) {
79
+ process.env[DUST_LOG_FILE] = path;
80
+ ownedDustLogFile = true;
81
+ }
82
+ activeFileSink = _sinkForTesting ?? new FileSink(path);
83
+ }
84
+ function createLogger(name) {
66
85
  return (...messages) => {
67
86
  init();
68
- if (!patterns || !matchesAny(name, patterns))
69
- return;
70
- write(formatLine(name, messages));
87
+ const line = formatLine(name, messages);
88
+ if (activeFileSink) {
89
+ activeFileSink.write(line);
90
+ }
91
+ if (patterns && matchesAny(name, patterns)) {
92
+ process.stdout.write(line);
93
+ }
71
94
  };
72
95
  }
73
96
  function isEnabled(name) {
@@ -77,10 +100,15 @@ function isEnabled(name) {
77
100
  function _reset() {
78
101
  initialized = false;
79
102
  patterns = null;
103
+ activeFileSink = null;
104
+ if (ownedDustLogFile) {
105
+ delete process.env[DUST_LOG_FILE];
106
+ ownedDustLogFile = false;
107
+ }
80
108
  }
81
109
  export {
82
- setLogScope,
83
110
  isEnabled,
111
+ enableFileLogs,
84
112
  createLogger,
85
113
  _reset
86
114
  };
@@ -21,25 +21,36 @@ function formatMetrics(metrics) {
21
21
  return parts.join(', ')
22
22
  }
23
23
 
24
- function getUncoveredLines(fileCoverage) {
24
+ function getGapLines(fileCoverage) {
25
+ const gapLines = new Set()
26
+
25
27
  const lineCoverage = fileCoverage.getLineCoverage()
28
+ for (const [lineStr, hits] of Object.entries(lineCoverage)) {
29
+ if (hits === 0) gapLines.add(Number.parseInt(lineStr, 10))
30
+ }
31
+
32
+ const { branchMap, b } = fileCoverage
33
+ for (const [id, branch] of Object.entries(branchMap)) {
34
+ if (b[id].some(hits => hits === 0)) {
35
+ gapLines.add(branch.loc.start.line)
36
+ }
37
+ }
38
+
39
+ const sorted = [...gapLines].sort((a, b) => a - b)
26
40
  const ranges = []
27
41
  let rangeStart = null
28
42
  let rangeEnd = null
29
43
 
30
- for (const [lineStr, hits] of Object.entries(lineCoverage)) {
31
- const line = Number.parseInt(lineStr, 10)
32
- if (hits === 0) {
33
- if (rangeStart === null) {
34
- rangeStart = line
35
- rangeEnd = line
36
- } else if (line === rangeEnd + 1) {
37
- rangeEnd = line
38
- } else {
39
- ranges.push([rangeStart, rangeEnd])
40
- rangeStart = line
41
- rangeEnd = line
42
- }
44
+ for (const line of sorted) {
45
+ if (rangeStart === null) {
46
+ rangeStart = line
47
+ rangeEnd = line
48
+ } else if (line === rangeEnd + 1) {
49
+ rangeEnd = line
50
+ } else {
51
+ ranges.push([rangeStart, rangeEnd])
52
+ rangeStart = line
53
+ rangeEnd = line
43
54
  }
44
55
  }
45
56
 
@@ -68,7 +79,12 @@ class IncompleteCoverageReporter extends ReportBase {
68
79
  },
69
80
  })
70
81
 
71
- if (incompleteFiles.length === 0) return
82
+ if (incompleteFiles.length === 0) {
83
+ const cw = context.writer.writeFile(null)
84
+ cw.println('✔ 100% coverage!')
85
+ cw.close()
86
+ return
87
+ }
72
88
 
73
89
  const cw = context.writer.writeFile(null)
74
90
  const count = incompleteFiles.length
@@ -78,7 +94,7 @@ class IncompleteCoverageReporter extends ReportBase {
78
94
  for (const file of incompleteFiles) {
79
95
  cw.println('')
80
96
  cw.println(`${file.name} (${formatMetrics(file.metrics)})`)
81
- for (const line of getUncoveredLines(file.fileCoverage)) {
97
+ for (const line of getGapLines(file.fileCoverage)) {
82
98
  cw.println(`- ${line}`)
83
99
  }
84
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {