@joshski/dust 0.1.30 → 0.1.31

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
@@ -589,6 +589,14 @@ function validateIdeaOpenQuestions(filePath, content) {
589
589
  line: currentQuestionLine
590
590
  });
591
591
  }
592
+ const headingText = line.slice(3).trimEnd();
593
+ if (headingText.toLowerCase() === "open questions" && headingText !== "Open Questions") {
594
+ violations.push({
595
+ file: filePath,
596
+ message: `Heading "${line.trimEnd()}" should be "## Open Questions"`,
597
+ line: i + 1
598
+ });
599
+ }
592
600
  inOpenQuestions = line === "## Open Questions";
593
601
  currentQuestionLine = null;
594
602
  continue;
@@ -105,15 +105,16 @@ async function createIdeaTask(fileSystem, dustPath, prefix, ideaSlug, openingSen
105
105
  return { filePath };
106
106
  }
107
107
  async function createRefineIdeaTask(fileSystem, dustPath, ideaSlug, description) {
108
- 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. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
108
+ 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).`, [
109
109
  "Idea is thoroughly researched with relevant codebase context",
110
110
  "Open questions are added for any ambiguous or underspecified aspects",
111
111
  "Idea file is updated with findings"
112
112
  ], { description });
113
113
  }
114
114
  async function createTaskFromIdea(fileSystem, dustPath, options) {
115
- return createIdeaTask(fileSystem, dustPath, "Create Task From Idea: ", options.ideaSlug, (ideaTitle) => `Create a well-defined task from this idea. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
115
+ return createIdeaTask(fileSystem, dustPath, "Create Task From Idea: ", options.ideaSlug, (ideaTitle) => `Create a well-defined task from this idea. Review \`.dust/goals/\` to link relevant goals and \`.dust/facts/\` for design decisions that should inform the task. See [${ideaTitle}](../ideas/${options.ideaSlug}.md).`, [
116
116
  "A new task is created in .dust/tasks/",
117
+ "Task's Goals section links to relevant goals from .dust/goals/",
117
118
  "The original idea is deleted or updated to reflect remaining scope"
118
119
  ], {
119
120
  description: options.description,
@@ -135,7 +136,7 @@ async function createCaptureIdeaTask(fileSystem, dustPath, title, description) {
135
136
  const filePath = `${dustPath}/tasks/${filename}`;
136
137
  const ideaFilename = titleToFilename(title);
137
138
  const ideaPath = `.dust/ideas/${ideaFilename}`;
138
- const content = renderTask(taskTitle, `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. The idea should have the title "${title}" and start from the following description:`, [
139
+ const content = renderTask(taskTitle, `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. The idea should have the title "${title}" and start from the following description:`, [
139
140
  `Idea file exists at ${ideaPath}`,
140
141
  `Idea file has an H1 title matching "${title}"`,
141
142
  "Idea includes relevant context from codebase exploration",
@@ -0,0 +1,90 @@
1
+ const { ReportBase } = require('istanbul-lib-report')
2
+
3
+ function isFull(metrics) {
4
+ return (
5
+ metrics.statements.pct === 100 &&
6
+ metrics.branches.pct === 100 &&
7
+ metrics.functions.pct === 100 &&
8
+ metrics.lines.pct === 100
9
+ )
10
+ }
11
+
12
+ function formatMetrics(metrics) {
13
+ const parts = []
14
+ if (metrics.lines.pct < 100) parts.push(`${metrics.lines.pct}% lines`)
15
+ if (metrics.statements.pct < 100)
16
+ parts.push(`${metrics.statements.pct}% statements`)
17
+ if (metrics.branches.pct < 100)
18
+ parts.push(`${metrics.branches.pct}% branches`)
19
+ if (metrics.functions.pct < 100)
20
+ parts.push(`${metrics.functions.pct}% functions`)
21
+ return parts.join(', ')
22
+ }
23
+
24
+ function getUncoveredLines(fileCoverage) {
25
+ const lineCoverage = fileCoverage.getLineCoverage()
26
+ const ranges = []
27
+ let rangeStart = null
28
+ let rangeEnd = null
29
+
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
+ }
43
+ }
44
+ }
45
+
46
+ if (rangeStart !== null) {
47
+ ranges.push([rangeStart, rangeEnd])
48
+ }
49
+
50
+ return ranges.map(([start, end]) =>
51
+ start === end ? `Line ${start}` : `Lines ${start}-${end}`
52
+ )
53
+ }
54
+
55
+ class IncompleteCoverageReporter extends ReportBase {
56
+ execute(context) {
57
+ const incompleteFiles = []
58
+ context.getTree().visit({
59
+ onDetail(node) {
60
+ const metrics = node.getCoverageSummary()
61
+ if (!metrics.isEmpty() && !isFull(metrics)) {
62
+ incompleteFiles.push({
63
+ name: node.getQualifiedName(),
64
+ metrics,
65
+ fileCoverage: node.getFileCoverage(),
66
+ })
67
+ }
68
+ },
69
+ })
70
+
71
+ if (incompleteFiles.length === 0) return
72
+
73
+ const cw = context.writer.writeFile(null)
74
+ const count = incompleteFiles.length
75
+ const label = count === 1 ? '1 file has' : `${count} files have`
76
+ cw.println(`${label} < 100% coverage:`)
77
+
78
+ for (const file of incompleteFiles) {
79
+ cw.println('')
80
+ cw.println(`${file.name} (${formatMetrics(file.metrics)})`)
81
+ for (const line of getUncoveredLines(file.fileCoverage)) {
82
+ cw.println(`- ${line}`)
83
+ }
84
+ }
85
+
86
+ cw.close()
87
+ }
88
+ }
89
+
90
+ module.exports = IncompleteCoverageReporter
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joshski/dust",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "Flow state for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,12 +10,14 @@
10
10
  "./workflow-tasks": {
11
11
  "import": "./dist/workflow-tasks.js",
12
12
  "types": "./dist/workflow-tasks.d.ts"
13
- }
13
+ },
14
+ "./istanbul/minimal-reporter": "./lib/istanbul/minimal-reporter.cjs"
14
15
  },
15
16
  "files": [
16
17
  "dist",
17
18
  "bin",
18
- "templates"
19
+ "templates",
20
+ "lib/istanbul/minimal-reporter.cjs"
19
21
  ],
20
22
  "repository": {
21
23
  "type": "git",