@joshski/dust 0.1.97 → 0.1.99

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.
@@ -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;
@@ -4,8 +4,9 @@ import { type Idea, type IdeaOpenQuestion, type IdeaOption, type ParsedIdeaConte
4
4
  import { extractTitle } from '../markdown/markdown-utilities';
5
5
  import { type Principle } from './principles';
6
6
  import { type Task } from './tasks';
7
+ import { type ParsedArtifact, type ParsedMarkdownLink, type ParsedSection, parseArtifact } from './parsed-artifact';
7
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';
8
- export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedCaptureIdeaTask, ParsedIdeaContent, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
9
+ export type { AllWorkflowTasks, CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, Fact, Idea, IdeaOpenQuestion, IdeaOption, OpenQuestionResponse, ParsedArtifact, ParsedCaptureIdeaTask, ParsedIdeaContent, ParsedMarkdownLink, ParsedSection, Principle, Task, WorkflowTaskMatch, WorkflowTaskType, };
9
10
  export interface TaskGraphNode {
10
11
  task: Task;
11
12
  workflowType: WorkflowTaskType | null;
@@ -17,9 +18,20 @@ export interface TaskGraph {
17
18
  to: string;
18
19
  }>;
19
20
  }
20
- export { CAPTURE_IDEA_PREFIX, extractTitle, findAllWorkflowTasks, ideaContentToMarkdown, parseIdeaContent, parseOpenQuestions, parseResolvedQuestions, };
21
+ export { CAPTURE_IDEA_PREFIX, extractTitle, findAllWorkflowTasks, ideaContentToMarkdown, parseArtifact, parseIdeaContent, parseOpenQuestions, parseResolvedQuestions, };
21
22
  export type { IdeaInProgress };
22
23
  export type ArtifactType = 'ideas' | 'tasks' | 'principles' | 'facts';
24
+ export declare const ARTIFACT_TYPES: ArtifactType[];
25
+ export declare const DUST_PATH_PREFIX = ".dust/";
26
+ /**
27
+ * Parses an artifact path into its type and slug components.
28
+ * This is the inverse of ArtifactsRepository.artifactPath().
29
+ * Works with relative paths (e.g., '.dust/tasks/my-task.md').
30
+ */
31
+ export declare function parseArtifactPath(path: string): {
32
+ type: ArtifactType;
33
+ slug: string;
34
+ } | null;
23
35
  export interface ReadOnlyArtifactsRepository {
24
36
  artifactPath(type: ArtifactType, slug: string): string;
25
37
  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
@@ -379,6 +379,143 @@ async function parseTask(fileSystem, dustPath, slug) {
379
379
  };
380
380
  }
381
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
+
382
519
  // lib/artifacts/workflow-tasks.ts
383
520
  var CAPTURE_IDEA_PREFIX = "Add Idea: ";
384
521
  var EXPEDITE_IDEA_PREFIX = "Expedite Idea: ";
@@ -756,6 +893,30 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
756
893
  }
757
894
 
758
895
  // lib/artifacts/index.ts
896
+ var ARTIFACT_TYPES = [
897
+ "facts",
898
+ "ideas",
899
+ "principles",
900
+ "tasks"
901
+ ];
902
+ var DUST_PATH_PREFIX = ".dust/";
903
+ function parseArtifactPath(path) {
904
+ if (!path.startsWith(DUST_PATH_PREFIX) || !path.endsWith(".md")) {
905
+ return null;
906
+ }
907
+ const withoutPrefix = path.slice(DUST_PATH_PREFIX.length);
908
+ const slashIndex = withoutPrefix.indexOf("/");
909
+ if (slashIndex === -1) {
910
+ return null;
911
+ }
912
+ const type = withoutPrefix.slice(0, slashIndex);
913
+ if (!ARTIFACT_TYPES.includes(type)) {
914
+ return null;
915
+ }
916
+ const filename = withoutPrefix.slice(slashIndex + 1);
917
+ const slug = filename.slice(0, -3);
918
+ return { type, slug };
919
+ }
759
920
  function buildReadOperations(fileSystem, dustPath) {
760
921
  return {
761
922
  artifactPath(type, slug) {
@@ -861,10 +1022,14 @@ export {
861
1022
  parseResolvedQuestions,
862
1023
  parseOpenQuestions,
863
1024
  parseIdeaContent,
1025
+ parseArtifactPath,
1026
+ parseArtifact,
864
1027
  ideaContentToMarkdown,
865
1028
  findAllWorkflowTasks,
866
1029
  extractTitle,
867
1030
  buildReadOnlyArtifactsRepository,
868
1031
  buildArtifactsRepository,
869
- CAPTURE_IDEA_PREFIX
1032
+ DUST_PATH_PREFIX,
1033
+ CAPTURE_IDEA_PREFIX,
1034
+ ARTIFACT_TYPES
870
1035
  };
@@ -1,17 +1,17 @@
1
1
  /**
2
- * Checks Audit - Pure functions for detecting tech stacks and suggesting checks.
2
+ * Checks Audit - Pure functions for detecting configured checks and suggesting improvements.
3
3
  *
4
4
  * This module provides the functional core for the checks-audit stock audit.
5
- * It detects the project's technology ecosystem, identifies configured checks,
6
- * parses CI configuration files, and suggests missing check categories.
5
+ * It identifies configured checks, parses CI configuration files, and suggests
6
+ * missing check categories.
7
+ *
8
+ * Tech stack detection is imported from lib/tech-stack/.
7
9
  */
8
10
  import type { DustSettings } from '../cli/types';
9
- export type Ecosystem = 'javascript' | 'python' | 'go' | 'rust' | 'ruby' | 'php' | 'elixir';
10
- export interface TechStackDetection {
11
- ecosystem: Ecosystem;
12
- indicators: string[];
13
- packageManager?: string;
14
- }
11
+ import type { Ecosystem, TechStackDetection } from '../tech-stack';
12
+ import { detectTechStack } from '../tech-stack';
13
+ export { detectTechStack };
14
+ export type { Ecosystem, TechStackDetection };
15
15
  export interface CheckOption {
16
16
  name: string;
17
17
  command: string;
@@ -29,10 +29,6 @@ export interface CIFileContent {
29
29
  path: string;
30
30
  content: string;
31
31
  }
32
- /**
33
- * Detects tech stacks based on project files.
34
- */
35
- export declare function detectTechStack(projectFiles: string[]): TechStackDetection[];
36
32
  /**
37
33
  * Extracts check categories from existing dust settings.
38
34
  */
@@ -51,5 +47,9 @@ export declare function suggestChecks(techStack: TechStackDetection[], projectFi
51
47
  export declare function renderCheckIdea(suggestion: CheckSuggestion, packageManager?: string): string;
52
48
  /**
53
49
  * Returns the checks-audit stock audit template.
50
+ *
51
+ * This template is tech-stack agnostic. Agents should discover appropriate
52
+ * checks by examining the project structure rather than receiving
53
+ * ecosystem-specific tool prescriptions.
54
54
  */
55
55
  export declare function checksAuditTemplate(): string;