@joshski/dust 0.1.96 → 0.1.98
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/agent-events.d.ts +13 -0
- package/dist/artifacts/ideas.d.ts +17 -0
- package/dist/artifacts/index.d.ts +15 -3
- package/dist/artifacts/parsed-artifact.d.ts +32 -0
- package/dist/artifacts.js +286 -29
- package/dist/audits.js +72 -38
- package/dist/bucket/repository-lifecycle.d.ts +38 -0
- package/dist/bucket/repository.d.ts +6 -4
- package/dist/cli/commands/focus.d.ts +1 -1
- package/dist/cli/process-runner.d.ts +35 -0
- package/dist/dust.js +1437 -930
- package/dist/lint/validators/content-validator.d.ts +5 -4
- package/dist/lint/validators/filename-validator.d.ts +2 -1
- package/dist/lint/validators/idea-validator.d.ts +4 -3
- package/dist/lint/validators/link-validator.d.ts +4 -3
- package/dist/lint/validators/principle-hierarchy.d.ts +3 -2
- package/dist/loop/events.d.ts +18 -1
- package/dist/loop/git-pull.d.ts +2 -2
- package/dist/loop/iteration.d.ts +4 -1
- package/dist/patch/fact.d.ts +15 -0
- package/dist/patch/idea.d.ts +23 -0
- package/dist/patch/index.d.ts +54 -0
- package/dist/patch/principle.d.ts +17 -0
- package/dist/patch/task.d.ts +25 -0
- package/dist/patch.js +1632 -0
- package/dist/types.d.ts +1 -1
- package/dist/validation/validation-pipeline.d.ts +42 -0
- package/dist/validation.js +491 -408
- package/package.json +5 -1
package/dist/agent-events.d.ts
CHANGED
|
@@ -29,6 +29,19 @@ export type AgentSessionEvent = {
|
|
|
29
29
|
} | {
|
|
30
30
|
type: 'command-event';
|
|
31
31
|
commandEvent: CommandEvent;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'preflight-started';
|
|
34
|
+
step: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: 'preflight-completed';
|
|
38
|
+
step: string;
|
|
39
|
+
output?: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'preflight-failed';
|
|
42
|
+
step: string;
|
|
43
|
+
output: string;
|
|
44
|
+
title?: string;
|
|
32
45
|
};
|
|
33
46
|
export interface EventMessage {
|
|
34
47
|
sequence: number;
|
|
@@ -14,6 +14,11 @@ export interface Idea {
|
|
|
14
14
|
content: string;
|
|
15
15
|
openQuestions: IdeaOpenQuestion[];
|
|
16
16
|
}
|
|
17
|
+
export interface ParsedIdeaContent {
|
|
18
|
+
title: string | null;
|
|
19
|
+
body: string;
|
|
20
|
+
openQuestions: IdeaOpenQuestion[];
|
|
21
|
+
}
|
|
17
22
|
/**
|
|
18
23
|
* Parses the ## Open Questions section from idea markdown content.
|
|
19
24
|
* Extracts each ### question heading and its #### option children.
|
|
@@ -23,3 +28,15 @@ export declare function parseOpenQuestions(content: string): IdeaOpenQuestion[];
|
|
|
23
28
|
* Parses an idea markdown file into a structured Idea object.
|
|
24
29
|
*/
|
|
25
30
|
export declare function parseIdea(fileSystem: ReadableFileSystem, dustPath: string, slug: string): Promise<Idea>;
|
|
31
|
+
/**
|
|
32
|
+
* Parses idea markdown into a structured object that can be bound to a UI
|
|
33
|
+
* and serialized back to markdown.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseIdeaContent(markdown: string): ParsedIdeaContent;
|
|
36
|
+
/**
|
|
37
|
+
* Serializes a ParsedIdeaContent back to markdown.
|
|
38
|
+
* Open Questions are appended as the last section.
|
|
39
|
+
*/
|
|
40
|
+
export declare function ideaContentToMarkdown(content: ParsedIdeaContent, options?: {
|
|
41
|
+
includeOpenQuestions?: boolean;
|
|
42
|
+
}): string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { FileSystem, ReadableFileSystem } from '../filesystem/types';
|
|
2
2
|
import { type Fact } from './facts';
|
|
3
|
-
import { type Idea, type IdeaOpenQuestion, type IdeaOption, parseOpenQuestions } from './ideas';
|
|
3
|
+
import { type Idea, type IdeaOpenQuestion, type IdeaOption, type ParsedIdeaContent, ideaContentToMarkdown, parseIdeaContent, parseOpenQuestions } from './ideas';
|
|
4
|
+
import { extractTitle } from '../markdown/markdown-utilities';
|
|
4
5
|
import { type Principle } from './principles';
|
|
5
6
|
import { type Task } from './tasks';
|
|
7
|
+
import { type ParsedArtifact, type ParsedMarkdownLink, type ParsedSection, parseArtifact } from './parsed-artifact';
|
|
6
8
|
import { type AllWorkflowTasks, CAPTURE_IDEA_PREFIX, type CreateIdeaTransitionTaskResult, type DecomposeIdeaOptions, findAllWorkflowTasks, type IdeaInProgress, type OpenQuestionResponse, type ParsedCaptureIdeaTask, parseResolvedQuestions, type WorkflowTaskMatch, type WorkflowTaskType } from './workflow-tasks';
|
|
7
|
-
export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
|
|
9
|
+
export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedArtifact, ParsedCaptureIdeaTask, ParsedIdeaContent, ParsedMarkdownLink, ParsedSection, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
|
|
8
10
|
export interface TaskGraphNode {
|
|
9
11
|
task: Task;
|
|
10
12
|
workflowType: WorkflowTaskType | null;
|
|
@@ -16,9 +18,19 @@ export interface TaskGraph {
|
|
|
16
18
|
to: string;
|
|
17
19
|
}>;
|
|
18
20
|
}
|
|
19
|
-
export { CAPTURE_IDEA_PREFIX, findAllWorkflowTasks, parseOpenQuestions, parseResolvedQuestions, };
|
|
21
|
+
export { CAPTURE_IDEA_PREFIX, extractTitle, findAllWorkflowTasks, ideaContentToMarkdown, parseArtifact, parseIdeaContent, parseOpenQuestions, parseResolvedQuestions, };
|
|
20
22
|
export type { IdeaInProgress };
|
|
21
23
|
export type ArtifactType = 'ideas' | 'tasks' | 'principles' | 'facts';
|
|
24
|
+
export declare const DUST_PATH_PREFIX = ".dust/";
|
|
25
|
+
/**
|
|
26
|
+
* Parses an artifact path into its type and slug components.
|
|
27
|
+
* This is the inverse of ArtifactsRepository.artifactPath().
|
|
28
|
+
* Works with relative paths (e.g., '.dust/tasks/my-task.md').
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseArtifactPath(path: string): {
|
|
31
|
+
type: ArtifactType;
|
|
32
|
+
slug: string;
|
|
33
|
+
} | null;
|
|
22
34
|
export interface ReadOnlyArtifactsRepository {
|
|
23
35
|
artifactPath(type: ArtifactType, slug: string): string;
|
|
24
36
|
parseIdea(options: {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed artifact types with positional metadata for validation.
|
|
3
|
+
*
|
|
4
|
+
* These types enrich artifact parsing with line numbers to support
|
|
5
|
+
* single-pass validation and better error reporting.
|
|
6
|
+
*/
|
|
7
|
+
export interface ParsedMarkdownLink {
|
|
8
|
+
text: string;
|
|
9
|
+
target: string;
|
|
10
|
+
line: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ParsedSection {
|
|
13
|
+
heading: string;
|
|
14
|
+
level: number;
|
|
15
|
+
startLine: number;
|
|
16
|
+
endLine: number;
|
|
17
|
+
links: ParsedMarkdownLink[];
|
|
18
|
+
}
|
|
19
|
+
export interface ParsedArtifact {
|
|
20
|
+
filePath: string;
|
|
21
|
+
rawContent: string;
|
|
22
|
+
title: string | null;
|
|
23
|
+
titleLine: number | null;
|
|
24
|
+
openingSentence: string | null;
|
|
25
|
+
openingSentenceLine: number | null;
|
|
26
|
+
sections: ParsedSection[];
|
|
27
|
+
allLinks: ParsedMarkdownLink[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parses markdown content into a ParsedArtifact with positional metadata.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseArtifact(filePath: string, content: string): ParsedArtifact;
|
package/dist/artifacts.js
CHANGED
|
@@ -4,50 +4,68 @@ function extractTitle(content) {
|
|
|
4
4
|
return match ? match[1].trim() : null;
|
|
5
5
|
}
|
|
6
6
|
var MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
7
|
-
function
|
|
8
|
-
const lines = content.split(`
|
|
9
|
-
`);
|
|
10
|
-
let h1Index = -1;
|
|
7
|
+
function findH1Index(lines) {
|
|
11
8
|
for (let i = 0;i < lines.length; i++) {
|
|
12
9
|
if (lines[i].match(/^#\s+.+$/)) {
|
|
13
|
-
|
|
14
|
-
break;
|
|
10
|
+
return i;
|
|
15
11
|
}
|
|
16
12
|
}
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
return -1;
|
|
14
|
+
}
|
|
15
|
+
function findFirstNonBlankLineAfter(lines, startIndex) {
|
|
16
|
+
for (let i = startIndex + 1;i < lines.length; i++) {
|
|
17
|
+
if (lines[i].trim() !== "") {
|
|
18
|
+
return i;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return -1;
|
|
22
|
+
}
|
|
23
|
+
var LIST_ITEM_PREFIXES = ["-", "*", "+"];
|
|
24
|
+
var STRUCTURAL_PREFIXES = ["#", "```", ">"];
|
|
25
|
+
function isStructuralElement(line) {
|
|
26
|
+
if (STRUCTURAL_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (LIST_ITEM_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
30
|
+
return true;
|
|
19
31
|
}
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
return /^\d+\./.test(line);
|
|
33
|
+
}
|
|
34
|
+
function isBlockBreak(line) {
|
|
35
|
+
return STRUCTURAL_PREFIXES.some((prefix) => line.startsWith(prefix));
|
|
36
|
+
}
|
|
37
|
+
function collectParagraph(lines, startIndex) {
|
|
38
|
+
const parts = [];
|
|
39
|
+
for (let i = startIndex;i < lines.length; i++) {
|
|
22
40
|
const line = lines[i].trim();
|
|
23
|
-
if (line
|
|
24
|
-
paragraphStart = i;
|
|
41
|
+
if (line === "" || isBlockBreak(line)) {
|
|
25
42
|
break;
|
|
26
43
|
}
|
|
44
|
+
parts.push(line);
|
|
27
45
|
}
|
|
28
|
-
|
|
46
|
+
return parts.join(" ");
|
|
47
|
+
}
|
|
48
|
+
function extractFirstSentence(paragraph) {
|
|
49
|
+
const match = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
50
|
+
return match ? match[1] : null;
|
|
51
|
+
}
|
|
52
|
+
function extractOpeningSentence(content) {
|
|
53
|
+
const lines = content.split(`
|
|
54
|
+
`);
|
|
55
|
+
const h1Index = findH1Index(lines);
|
|
56
|
+
if (h1Index === -1) {
|
|
29
57
|
return null;
|
|
30
58
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (trimmedFirstLine.startsWith("#") || trimmedFirstLine.startsWith("-") || trimmedFirstLine.startsWith("*") || trimmedFirstLine.startsWith("+") || trimmedFirstLine.match(/^\d+\./) || trimmedFirstLine.startsWith("```") || trimmedFirstLine.startsWith(">")) {
|
|
59
|
+
const paragraphStart = findFirstNonBlankLineAfter(lines, h1Index);
|
|
60
|
+
if (paragraphStart === -1) {
|
|
34
61
|
return null;
|
|
35
62
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const line = lines[i].trim();
|
|
39
|
-
if (line === "")
|
|
40
|
-
break;
|
|
41
|
-
if (line.startsWith("#") || line.startsWith("```") || line.startsWith(">")) {
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
paragraph += (paragraph ? " " : "") + line;
|
|
45
|
-
}
|
|
46
|
-
const sentenceMatch = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
47
|
-
if (!sentenceMatch) {
|
|
63
|
+
const trimmedFirstLine = lines[paragraphStart].trim();
|
|
64
|
+
if (isStructuralElement(trimmedFirstLine)) {
|
|
48
65
|
return null;
|
|
49
66
|
}
|
|
50
|
-
|
|
67
|
+
const paragraph = collectParagraph(lines, paragraphStart);
|
|
68
|
+
return extractFirstSentence(paragraph);
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
// lib/artifacts/facts.ts
|
|
@@ -162,6 +180,83 @@ async function parseIdea(fileSystem, dustPath, slug) {
|
|
|
162
180
|
openQuestions
|
|
163
181
|
};
|
|
164
182
|
}
|
|
183
|
+
function stripOpenQuestionsSection(content) {
|
|
184
|
+
const lines = content.split(`
|
|
185
|
+
`);
|
|
186
|
+
const result = [];
|
|
187
|
+
let inOpenQuestions = false;
|
|
188
|
+
let inCodeFence = false;
|
|
189
|
+
for (const line of lines) {
|
|
190
|
+
if (line.startsWith("```")) {
|
|
191
|
+
inCodeFence = !inCodeFence;
|
|
192
|
+
if (!inOpenQuestions)
|
|
193
|
+
result.push(line);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (inCodeFence) {
|
|
197
|
+
if (!inOpenQuestions)
|
|
198
|
+
result.push(line);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (line.startsWith("## ")) {
|
|
202
|
+
inOpenQuestions = line.trimEnd() === "## Open Questions";
|
|
203
|
+
if (!inOpenQuestions)
|
|
204
|
+
result.push(line);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!inOpenQuestions) {
|
|
208
|
+
result.push(line);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
|
212
|
+
result.pop();
|
|
213
|
+
}
|
|
214
|
+
return result.join(`
|
|
215
|
+
`) + `
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
function stripTitle(content) {
|
|
219
|
+
const match = content.match(/^#\s+.+\n+/);
|
|
220
|
+
if (!match)
|
|
221
|
+
return content;
|
|
222
|
+
return content.slice(match[0].length);
|
|
223
|
+
}
|
|
224
|
+
function parseIdeaContent(markdown) {
|
|
225
|
+
const title = extractTitle(markdown);
|
|
226
|
+
const openQuestions = parseOpenQuestions(markdown);
|
|
227
|
+
const body = stripTitle(stripOpenQuestionsSection(markdown));
|
|
228
|
+
return { title, body, openQuestions };
|
|
229
|
+
}
|
|
230
|
+
function ideaContentToMarkdown(content, options) {
|
|
231
|
+
const includeOQ = options?.includeOpenQuestions ?? true;
|
|
232
|
+
const parts = [];
|
|
233
|
+
if (content.title) {
|
|
234
|
+
parts.push(`# ${content.title}`);
|
|
235
|
+
parts.push("");
|
|
236
|
+
}
|
|
237
|
+
if (content.body) {
|
|
238
|
+
parts.push(content.body.trimEnd());
|
|
239
|
+
parts.push("");
|
|
240
|
+
}
|
|
241
|
+
if (includeOQ && content.openQuestions.length > 0) {
|
|
242
|
+
parts.push("## Open Questions");
|
|
243
|
+
parts.push("");
|
|
244
|
+
for (const q of content.openQuestions) {
|
|
245
|
+
parts.push(`### ${q.question}`);
|
|
246
|
+
parts.push("");
|
|
247
|
+
for (const o of q.options) {
|
|
248
|
+
parts.push(`#### ${o.name}`);
|
|
249
|
+
parts.push("");
|
|
250
|
+
if (o.description) {
|
|
251
|
+
parts.push(o.description);
|
|
252
|
+
parts.push("");
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return parts.join(`
|
|
258
|
+
`);
|
|
259
|
+
}
|
|
165
260
|
|
|
166
261
|
// lib/artifacts/principles.ts
|
|
167
262
|
function extractLinksFromSection(content, sectionHeading) {
|
|
@@ -284,6 +379,143 @@ async function parseTask(fileSystem, dustPath, slug) {
|
|
|
284
379
|
};
|
|
285
380
|
}
|
|
286
381
|
|
|
382
|
+
// lib/artifacts/parsed-artifact.ts
|
|
383
|
+
var MARKDOWN_LINK_PATTERN2 = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
384
|
+
function parseArtifact(filePath, content) {
|
|
385
|
+
const lines = content.split(`
|
|
386
|
+
`);
|
|
387
|
+
let title = null;
|
|
388
|
+
let titleLine = null;
|
|
389
|
+
let openingSentence = null;
|
|
390
|
+
let openingSentenceLine = null;
|
|
391
|
+
const sections = [];
|
|
392
|
+
const allLinks = [];
|
|
393
|
+
let currentSection = null;
|
|
394
|
+
let inCodeFence = false;
|
|
395
|
+
let openingSentenceResolved = false;
|
|
396
|
+
for (let i = 0;i < lines.length; i++) {
|
|
397
|
+
const line = lines[i];
|
|
398
|
+
const lineNumber = i + 1;
|
|
399
|
+
if (line.startsWith("```")) {
|
|
400
|
+
inCodeFence = !inCodeFence;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (inCodeFence) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const h1Match = line.match(/^#\s+(.+)$/);
|
|
407
|
+
if (h1Match) {
|
|
408
|
+
if (title === null) {
|
|
409
|
+
title = h1Match[1].trim();
|
|
410
|
+
titleLine = lineNumber;
|
|
411
|
+
} else {
|
|
412
|
+
if (currentSection !== null) {
|
|
413
|
+
currentSection.endLine = findLastNonEmptyLine(lines, currentSection.startLine, lineNumber - 2);
|
|
414
|
+
sections.push(currentSection);
|
|
415
|
+
currentSection = null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const headingMatch = line.match(/^(#{2,6})\s+(.+)$/);
|
|
421
|
+
if (headingMatch) {
|
|
422
|
+
openingSentenceResolved = true;
|
|
423
|
+
if (currentSection !== null) {
|
|
424
|
+
currentSection.endLine = findLastNonEmptyLine(lines, currentSection.startLine, lineNumber - 2);
|
|
425
|
+
sections.push(currentSection);
|
|
426
|
+
}
|
|
427
|
+
currentSection = {
|
|
428
|
+
heading: headingMatch[2].trim(),
|
|
429
|
+
level: headingMatch[1].length,
|
|
430
|
+
startLine: lineNumber,
|
|
431
|
+
endLine: -1,
|
|
432
|
+
links: []
|
|
433
|
+
};
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (shouldCheckForOpeningSentence(title, openingSentenceResolved, line)) {
|
|
437
|
+
const result = tryExtractOpeningSentence(lines, i);
|
|
438
|
+
openingSentence = result.sentence;
|
|
439
|
+
openingSentenceLine = result.sentence !== null ? lineNumber : null;
|
|
440
|
+
openingSentenceResolved = true;
|
|
441
|
+
}
|
|
442
|
+
const linkMatches = line.matchAll(MARKDOWN_LINK_PATTERN2);
|
|
443
|
+
for (const match of linkMatches) {
|
|
444
|
+
const link = {
|
|
445
|
+
text: match[1],
|
|
446
|
+
target: match[2],
|
|
447
|
+
line: lineNumber
|
|
448
|
+
};
|
|
449
|
+
allLinks.push(link);
|
|
450
|
+
if (currentSection !== null) {
|
|
451
|
+
currentSection.links.push(link);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (currentSection !== null) {
|
|
456
|
+
currentSection.endLine = findLastNonEmptyLine(lines, currentSection.startLine, lines.length - 1);
|
|
457
|
+
sections.push(currentSection);
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
filePath,
|
|
461
|
+
rawContent: content,
|
|
462
|
+
title,
|
|
463
|
+
titleLine,
|
|
464
|
+
openingSentence,
|
|
465
|
+
openingSentenceLine,
|
|
466
|
+
sections,
|
|
467
|
+
allLinks
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function shouldCheckForOpeningSentence(title, resolved, line) {
|
|
471
|
+
if (title === null || resolved) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
const trimmed = line.trim();
|
|
475
|
+
return trimmed !== "" && !isStructuralElement2(trimmed);
|
|
476
|
+
}
|
|
477
|
+
function tryExtractOpeningSentence(lines, startIndex) {
|
|
478
|
+
const paragraph = collectParagraph2(lines, startIndex);
|
|
479
|
+
return { sentence: extractFirstSentence2(paragraph) };
|
|
480
|
+
}
|
|
481
|
+
function findLastNonEmptyLine(lines, contentStartIndex, fromIndex) {
|
|
482
|
+
for (let i = fromIndex;i >= contentStartIndex; i--) {
|
|
483
|
+
if (lines[i].trim() !== "") {
|
|
484
|
+
return i + 1;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return contentStartIndex;
|
|
488
|
+
}
|
|
489
|
+
var LIST_ITEM_PREFIXES2 = ["-", "*", "+"];
|
|
490
|
+
var STRUCTURAL_PREFIXES2 = ["#", "```", ">"];
|
|
491
|
+
function isStructuralElement2(line) {
|
|
492
|
+
if (STRUCTURAL_PREFIXES2.some((prefix) => line.startsWith(prefix))) {
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
if (LIST_ITEM_PREFIXES2.some((prefix) => line.startsWith(prefix))) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
return /^\d+\./.test(line);
|
|
499
|
+
}
|
|
500
|
+
function isBlockBreak2(line) {
|
|
501
|
+
return STRUCTURAL_PREFIXES2.some((prefix) => line.startsWith(prefix));
|
|
502
|
+
}
|
|
503
|
+
function collectParagraph2(lines, startIndex) {
|
|
504
|
+
const parts = [];
|
|
505
|
+
for (let i = startIndex;i < lines.length; i++) {
|
|
506
|
+
const line = lines[i].trim();
|
|
507
|
+
if (line === "" || isBlockBreak2(line)) {
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
parts.push(line);
|
|
511
|
+
}
|
|
512
|
+
return parts.join(" ");
|
|
513
|
+
}
|
|
514
|
+
function extractFirstSentence2(paragraph) {
|
|
515
|
+
const match = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
516
|
+
return match ? match[1] : null;
|
|
517
|
+
}
|
|
518
|
+
|
|
287
519
|
// lib/artifacts/workflow-tasks.ts
|
|
288
520
|
var CAPTURE_IDEA_PREFIX = "Add Idea: ";
|
|
289
521
|
var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
|
|
@@ -661,6 +893,25 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
|
|
|
661
893
|
}
|
|
662
894
|
|
|
663
895
|
// lib/artifacts/index.ts
|
|
896
|
+
var ARTIFACT_TYPES = ["ideas", "tasks", "principles", "facts"];
|
|
897
|
+
var DUST_PATH_PREFIX = ".dust/";
|
|
898
|
+
function parseArtifactPath(path) {
|
|
899
|
+
if (!path.startsWith(DUST_PATH_PREFIX) || !path.endsWith(".md")) {
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
const withoutPrefix = path.slice(DUST_PATH_PREFIX.length);
|
|
903
|
+
const slashIndex = withoutPrefix.indexOf("/");
|
|
904
|
+
if (slashIndex === -1) {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
const type = withoutPrefix.slice(0, slashIndex);
|
|
908
|
+
if (!ARTIFACT_TYPES.includes(type)) {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
const filename = withoutPrefix.slice(slashIndex + 1);
|
|
912
|
+
const slug = filename.slice(0, -3);
|
|
913
|
+
return { type, slug };
|
|
914
|
+
}
|
|
664
915
|
function buildReadOperations(fileSystem, dustPath) {
|
|
665
916
|
return {
|
|
666
917
|
artifactPath(type, slug) {
|
|
@@ -765,8 +1016,14 @@ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
|
|
|
765
1016
|
export {
|
|
766
1017
|
parseResolvedQuestions,
|
|
767
1018
|
parseOpenQuestions,
|
|
1019
|
+
parseIdeaContent,
|
|
1020
|
+
parseArtifactPath,
|
|
1021
|
+
parseArtifact,
|
|
1022
|
+
ideaContentToMarkdown,
|
|
768
1023
|
findAllWorkflowTasks,
|
|
1024
|
+
extractTitle,
|
|
769
1025
|
buildReadOnlyArtifactsRepository,
|
|
770
1026
|
buildArtifactsRepository,
|
|
1027
|
+
DUST_PATH_PREFIX,
|
|
771
1028
|
CAPTURE_IDEA_PREFIX
|
|
772
1029
|
};
|
package/dist/audits.js
CHANGED
|
@@ -6,50 +6,68 @@ function extractTitle(content) {
|
|
|
6
6
|
const match = content.match(/^#\s+(.+)$/m);
|
|
7
7
|
return match ? match[1].trim() : null;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
const lines = content.split(`
|
|
11
|
-
`);
|
|
12
|
-
let h1Index = -1;
|
|
9
|
+
function findH1Index(lines) {
|
|
13
10
|
for (let i = 0;i < lines.length; i++) {
|
|
14
11
|
if (lines[i].match(/^#\s+.+$/)) {
|
|
15
|
-
|
|
16
|
-
break;
|
|
12
|
+
return i;
|
|
17
13
|
}
|
|
18
14
|
}
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
return -1;
|
|
16
|
+
}
|
|
17
|
+
function findFirstNonBlankLineAfter(lines, startIndex) {
|
|
18
|
+
for (let i = startIndex + 1;i < lines.length; i++) {
|
|
19
|
+
if (lines[i].trim() !== "") {
|
|
20
|
+
return i;
|
|
21
|
+
}
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
return -1;
|
|
24
|
+
}
|
|
25
|
+
var LIST_ITEM_PREFIXES = ["-", "*", "+"];
|
|
26
|
+
var STRUCTURAL_PREFIXES = ["#", "```", ">"];
|
|
27
|
+
function isStructuralElement(line) {
|
|
28
|
+
if (STRUCTURAL_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (LIST_ITEM_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return /^\d+\./.test(line);
|
|
35
|
+
}
|
|
36
|
+
function isBlockBreak(line) {
|
|
37
|
+
return STRUCTURAL_PREFIXES.some((prefix) => line.startsWith(prefix));
|
|
38
|
+
}
|
|
39
|
+
function collectParagraph(lines, startIndex) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (let i = startIndex;i < lines.length; i++) {
|
|
24
42
|
const line = lines[i].trim();
|
|
25
|
-
if (line
|
|
26
|
-
paragraphStart = i;
|
|
43
|
+
if (line === "" || isBlockBreak(line)) {
|
|
27
44
|
break;
|
|
28
45
|
}
|
|
46
|
+
parts.push(line);
|
|
29
47
|
}
|
|
30
|
-
|
|
48
|
+
return parts.join(" ");
|
|
49
|
+
}
|
|
50
|
+
function extractFirstSentence(paragraph) {
|
|
51
|
+
const match = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
52
|
+
return match ? match[1] : null;
|
|
53
|
+
}
|
|
54
|
+
function extractOpeningSentence(content) {
|
|
55
|
+
const lines = content.split(`
|
|
56
|
+
`);
|
|
57
|
+
const h1Index = findH1Index(lines);
|
|
58
|
+
if (h1Index === -1) {
|
|
31
59
|
return null;
|
|
32
60
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
if (trimmedFirstLine.startsWith("#") || trimmedFirstLine.startsWith("-") || trimmedFirstLine.startsWith("*") || trimmedFirstLine.startsWith("+") || trimmedFirstLine.match(/^\d+\./) || trimmedFirstLine.startsWith("```") || trimmedFirstLine.startsWith(">")) {
|
|
61
|
+
const paragraphStart = findFirstNonBlankLineAfter(lines, h1Index);
|
|
62
|
+
if (paragraphStart === -1) {
|
|
36
63
|
return null;
|
|
37
64
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const line = lines[i].trim();
|
|
41
|
-
if (line === "")
|
|
42
|
-
break;
|
|
43
|
-
if (line.startsWith("#") || line.startsWith("```") || line.startsWith(">")) {
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
paragraph += (paragraph ? " " : "") + line;
|
|
47
|
-
}
|
|
48
|
-
const sentenceMatch = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
49
|
-
if (!sentenceMatch) {
|
|
65
|
+
const trimmedFirstLine = lines[paragraphStart].trim();
|
|
66
|
+
if (isStructuralElement(trimmedFirstLine)) {
|
|
50
67
|
return null;
|
|
51
68
|
}
|
|
52
|
-
|
|
69
|
+
const paragraph = collectParagraph(lines, paragraphStart);
|
|
70
|
+
return extractFirstSentence(paragraph);
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
// lib/cli/dedent.ts
|
|
@@ -214,19 +232,33 @@ function coverageExclusions() {
|
|
|
214
232
|
return dedent`
|
|
215
233
|
# Coverage Exclusions
|
|
216
234
|
|
|
217
|
-
|
|
235
|
+
Audit all coverage exclusions to identify opportunities for removal through refactoring.
|
|
218
236
|
|
|
219
237
|
${ideasHint}
|
|
220
238
|
|
|
221
239
|
## Scope
|
|
222
240
|
|
|
223
|
-
|
|
241
|
+
Search for exclusions in both configuration and source code:
|
|
242
|
+
|
|
243
|
+
1. **Configuration-level exclusions** - Review test framework configuration for file/directory exclusion patterns
|
|
244
|
+
2. **Inline escape directives** - Search for comments that exclude code from coverage (e.g., ignore directives in source files)
|
|
245
|
+
3. **Test files** - Include test code in the search; inline directives may hide flaky or unclear test logic
|
|
246
|
+
|
|
247
|
+
## Analysis
|
|
248
|
+
|
|
249
|
+
For each exclusion found:
|
|
224
250
|
|
|
225
|
-
1. **
|
|
226
|
-
2. **
|
|
227
|
-
3. **
|
|
228
|
-
|
|
229
|
-
|
|
251
|
+
1. **Document the location** - File path and line number (or config section)
|
|
252
|
+
2. **Identify the reason** - Why was this exclusion added?
|
|
253
|
+
3. **Categorize by justification**:
|
|
254
|
+
- Native wrapper (code that wraps platform APIs with no testable logic)
|
|
255
|
+
- Defensive guard (unreachable error handling for type safety)
|
|
256
|
+
- Integration boundary (code that requires external systems)
|
|
257
|
+
- Tooling limitation (coverage tool bug or limitation)
|
|
258
|
+
- Technical debt (code that should be testable but isn't)
|
|
259
|
+
- Unknown (no clear justification found)
|
|
260
|
+
4. **Label justification quality** - Is the justification well-documented, reasonable, or questionable?
|
|
261
|
+
5. **Evaluate removal potential** - Can the exclusion be removed through decoupling or refactoring?
|
|
230
262
|
|
|
231
263
|
## Principles
|
|
232
264
|
|
|
@@ -241,9 +273,11 @@ function coverageExclusions() {
|
|
|
241
273
|
|
|
242
274
|
## Definition of Done
|
|
243
275
|
|
|
244
|
-
- Identified all coverage exclusions
|
|
276
|
+
- Identified all configuration-level coverage exclusions
|
|
277
|
+
- Searched source and test files for inline escape directives
|
|
245
278
|
- Documented the reason each exclusion exists
|
|
246
|
-
-
|
|
279
|
+
- Categorized each exclusion by justification type
|
|
280
|
+
- Labeled justification quality for visibility
|
|
247
281
|
- Identified exclusions that could be removed through decoupling
|
|
248
282
|
- Proposed ideas for refactoring where feasible
|
|
249
283
|
`;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository lifecycle state type for dust bucket.
|
|
3
|
+
*
|
|
4
|
+
* Represents all valid states in the repository lifecycle as a discriminated union,
|
|
5
|
+
* making invalid state combinations unrepresentable.
|
|
6
|
+
*/
|
|
7
|
+
export type RepositoryLifecycleState = {
|
|
8
|
+
type: 'idle';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'starting';
|
|
11
|
+
} | {
|
|
12
|
+
type: 'running';
|
|
13
|
+
loopPromise: Promise<void>;
|
|
14
|
+
cancel: () => void;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'stopping';
|
|
17
|
+
} | {
|
|
18
|
+
type: 'stopped';
|
|
19
|
+
};
|
|
20
|
+
export type LifecycleAction = {
|
|
21
|
+
type: 'start';
|
|
22
|
+
} | {
|
|
23
|
+
type: 'started';
|
|
24
|
+
loopPromise: Promise<void>;
|
|
25
|
+
cancel: () => void;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'stop';
|
|
28
|
+
} | {
|
|
29
|
+
type: 'stopped';
|
|
30
|
+
};
|
|
31
|
+
export type TransitionResult = {
|
|
32
|
+
ok: true;
|
|
33
|
+
state: RepositoryLifecycleState;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
error: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function transition(current: RepositoryLifecycleState, action: LifecycleAction): TransitionResult;
|