@joshski/dust 0.1.94 → 0.1.95

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.
@@ -19,7 +19,7 @@ export interface TaskGraph {
19
19
  export { CAPTURE_IDEA_PREFIX, findAllWorkflowTasks, parseOpenQuestions, parseResolvedQuestions, };
20
20
  export type { IdeaInProgress };
21
21
  export type ArtifactType = 'ideas' | 'tasks' | 'principles' | 'facts';
22
- export interface ArtifactsRepository {
22
+ export interface ReadOnlyArtifactsRepository {
23
23
  artifactPath(type: ArtifactType, slug: string): string;
24
24
  parseIdea(options: {
25
25
  slug: string;
@@ -37,6 +37,15 @@ export interface ArtifactsRepository {
37
37
  slug: string;
38
38
  }): Promise<Task>;
39
39
  listTasks(): Promise<string[]>;
40
+ findWorkflowTaskForIdea(options: {
41
+ ideaSlug: string;
42
+ }): Promise<WorkflowTaskMatch | null>;
43
+ parseCaptureIdeaTask(options: {
44
+ taskSlug: string;
45
+ }): Promise<ParsedCaptureIdeaTask | null>;
46
+ buildTaskGraph(): Promise<TaskGraph>;
47
+ }
48
+ export interface ArtifactsRepository extends ReadOnlyArtifactsRepository {
40
49
  createRefineIdeaTask(options: {
41
50
  ideaSlug: string;
42
51
  description?: string;
@@ -51,19 +60,17 @@ export interface ArtifactsRepository {
51
60
  description?: string;
52
61
  dustCommand?: string;
53
62
  }): Promise<CreateIdeaTransitionTaskResult>;
63
+ createExpediteIdeaTask(options: {
64
+ ideaSlug: string;
65
+ description?: string;
66
+ dustCommand?: string;
67
+ }): Promise<CreateIdeaTransitionTaskResult>;
54
68
  createIdeaTask(options: {
55
69
  title: string;
56
70
  description: string;
57
71
  expedite?: boolean;
58
72
  dustCommand?: string;
59
73
  }): Promise<CreateIdeaTransitionTaskResult>;
60
- findWorkflowTaskForIdea(options: {
61
- ideaSlug: string;
62
- }): Promise<WorkflowTaskMatch | null>;
63
- parseCaptureIdeaTask(options: {
64
- taskSlug: string;
65
- }): Promise<ParsedCaptureIdeaTask | null>;
66
- buildTaskGraph(): Promise<TaskGraph>;
67
74
  }
68
75
  export declare function buildArtifactsRepository(fileSystem: FileSystem, dustPath: string): ArtifactsRepository;
69
- export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): Pick<ArtifactsRepository, 'artifactPath' | 'parseIdea' | 'listIdeas' | 'parsePrinciple' | 'listPrinciples' | 'parseFact' | 'listFacts' | 'parseTask' | 'listTasks' | 'findWorkflowTaskForIdea' | 'parseCaptureIdeaTask' | 'buildTaskGraph'>;
76
+ export declare function buildReadOnlyArtifactsRepository(fileSystem: ReadableFileSystem, dustPath: string): ReadOnlyArtifactsRepository;
@@ -21,7 +21,7 @@ export interface ParsedCaptureIdeaTask {
21
21
  * 6. Add .md extension
22
22
  */
23
23
  export declare function titleToFilename(title: string): string;
24
- export type WorkflowTaskType = 'refine-idea' | 'decompose-idea' | 'shelve-idea';
24
+ export type WorkflowTaskType = 'refine-idea' | 'decompose-idea' | 'shelve-idea' | 'expedite-idea';
25
25
  export interface WorkflowTaskMatch {
26
26
  type: WorkflowTaskType;
27
27
  ideaSlug: string;
@@ -50,6 +50,7 @@ export declare function parseResolvedQuestions(content: string): OpenQuestionRes
50
50
  export declare function createRefineIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string, openQuestionResponses?: OpenQuestionResponse[], dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
51
51
  export declare function decomposeIdea(fileSystem: FileSystem, dustPath: string, options: DecomposeIdeaOptions, dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
52
52
  export declare function createShelveIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string, _dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
53
+ export declare function createExpediteIdeaTask(fileSystem: FileSystem, dustPath: string, ideaSlug: string, description?: string, dustCommand?: string): Promise<CreateIdeaTransitionTaskResult>;
53
54
  export declare function createIdeaTask(fileSystem: FileSystem, dustPath: string, options: {
54
55
  title: string;
55
56
  description: string;
package/dist/artifacts.js CHANGED
@@ -254,9 +254,9 @@ function extractDefinitionOfDone(content) {
254
254
  continue;
255
255
  if (line.startsWith("# "))
256
256
  break;
257
- const checklistMatch = line.match(/^-\s+\[[x\s]\]\s+(.+)$/i);
258
- if (checklistMatch) {
259
- items.push(checklistMatch[1].trim());
257
+ const listMatch = line.match(/^-\s+(.+)$/);
258
+ if (listMatch) {
259
+ items.push(listMatch[1].trim());
260
260
  }
261
261
  }
262
262
  return items;
@@ -307,7 +307,8 @@ async function readWorkflowHint(fileSystem, dustPath, workflowType) {
307
307
  var WORKFLOW_SECTION_HEADINGS = [
308
308
  { type: "refine-idea", heading: "Refines Idea" },
309
309
  { type: "decompose-idea", heading: "Decomposes Idea" },
310
- { type: "shelve-idea", heading: "Shelves Idea" }
310
+ { type: "shelve-idea", heading: "Shelves Idea" },
311
+ { type: "expedite-idea", heading: "Expedites Idea" }
311
312
  ];
312
313
  function extractIdeaSlugFromSection(content, sectionHeading) {
313
314
  const lines = content.split(`
@@ -348,7 +349,7 @@ async function findAllWorkflowTasks(fileSystem, dustPath) {
348
349
  return { captureIdeaTasks, workflowTasksByIdeaSlug };
349
350
  }
350
351
  const files = await fileSystem.readdir(tasksPath);
351
- for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
352
+ for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
352
353
  const content = await fileSystem.readFile(`${tasksPath}/${file}`);
353
354
  const titleMatch = content.match(/^#\s+(.+)$/m);
354
355
  if (!titleMatch)
@@ -390,7 +391,7 @@ async function findWorkflowTaskForIdea(fileSystem, dustPath, ideaSlug) {
390
391
  return null;
391
392
  }
392
393
  const files = await fileSystem.readdir(tasksPath);
393
- for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
394
+ for (const file of files.filter((f) => f.endsWith(".md")).toSorted()) {
394
395
  const content = await fileSystem.readFile(`${tasksPath}/${file}`);
395
396
  for (const { type, heading } of WORKFLOW_SECTION_HEADINGS) {
396
397
  const linkedSlug = extractIdeaSlugFromSection(content, heading);
@@ -507,7 +508,7 @@ ${repositoryHintsSection}
507
508
 
508
509
  ## Definition of Done
509
510
 
510
- ${definitionOfDone.map((item) => `- [ ] ${item}`).join(`
511
+ ${definitionOfDone.map((item) => `- ${item}`).join(`
511
512
  `)}
512
513
  `;
513
514
  }
@@ -553,6 +554,14 @@ async function decomposeIdea(fileSystem, dustPath, options, dustCommand) {
553
554
  async function createShelveIdeaTask(fileSystem, dustPath, ideaSlug, description, _dustCommand) {
554
555
  return createIdeaTransitionTask(fileSystem, dustPath, "shelve-idea", "Shelve Idea: ", ideaSlug, (ideaTitle) => `Archive this idea and remove it from the active backlog. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, ["Idea file is deleted", "Rationale is recorded in the commit message"], "Shelves Idea", { description });
555
556
  }
557
+ async function createExpediteIdeaTask(fileSystem, dustPath, ideaSlug, description, dustCommand) {
558
+ const cmd = dustCommand ?? "dust";
559
+ return createIdeaTransitionTask(fileSystem, dustPath, "expedite-idea", "Expedite Idea: ", ideaSlug, (ideaTitle) => `Research this idea briefly. If confident the implementation is straightforward (clear scope, minimal risk, no open questions), implement directly and commit. Otherwise, create one or more narrowly-scoped task files in \`.dust/tasks/\`. Run \`${cmd} principles\` and \`${cmd} facts\` for relevant context. See [${ideaTitle}](../ideas/${ideaSlug}.md).`, [
560
+ "Idea is implemented directly OR one or more new tasks are created in `.dust/tasks/`",
561
+ "If tasks were created, they link to relevant principles from `.dust/principles/`",
562
+ "Changes are committed with a clear commit message"
563
+ ], "Expedites Idea", { description });
564
+ }
556
565
  async function createIdeaTask(fileSystem, dustPath, options) {
557
566
  const { title, description, expedite, dustCommand } = options;
558
567
  const cmd = dustCommand ?? "dust";
@@ -584,9 +593,9 @@ ${repositoryHintsSection2}
584
593
 
585
594
  ## Definition of Done
586
595
 
587
- - [ ] Idea is implemented directly OR one or more new tasks are created in \`.dust/tasks/\`
588
- - [ ] If tasks were created, they link to relevant principles from \`.dust/principles/\`
589
- - [ ] Changes are committed with a clear commit message
596
+ - Idea is implemented directly OR one or more new tasks are created in \`.dust/tasks/\`
597
+ - If tasks were created, they link to relevant principles from \`.dust/principles/\`
598
+ - Changes are committed with a clear commit message
590
599
  `;
591
600
  await fileSystem.writeFile(filePath2, content2);
592
601
  return { filePath: filePath2 };
@@ -612,11 +621,11 @@ ${repositoryHintsSection}
612
621
 
613
622
  ## Definition of Done
614
623
 
615
- - [ ] One or more idea files are created in \`.dust/ideas/\`
616
- - [ ] Each idea file has an H1 title matching its content
617
- - [ ] Idea includes relevant context from codebase exploration
618
- - [ ] Open questions are added for any ambiguous or underspecified aspects
619
- - [ ] Open questions follow the required heading format and focus on high-value decisions
624
+ - One or more idea files are created in \`.dust/ideas/\`
625
+ - Each idea file has an H1 title matching its content
626
+ - Idea includes relevant context from codebase exploration
627
+ - Open questions are added for any ambiguous or underspecified aspects
628
+ - Open questions follow the required heading format and focus on high-value decisions
620
629
  `;
621
630
  await fileSystem.writeFile(filePath, content);
622
631
  return { filePath };
@@ -652,7 +661,7 @@ async function parseCaptureIdeaTask(fileSystem, dustPath, taskSlug) {
652
661
  }
653
662
 
654
663
  // lib/artifacts/index.ts
655
- function buildArtifactsRepository(fileSystem, dustPath) {
664
+ function buildReadOperations(fileSystem, dustPath) {
656
665
  return {
657
666
  artifactPath(type, slug) {
658
667
  return `${dustPath}/${type}/${slug}.md`;
@@ -666,7 +675,7 @@ function buildArtifactsRepository(fileSystem, dustPath) {
666
675
  return [];
667
676
  }
668
677
  const files = await fileSystem.readdir(ideasPath);
669
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
678
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
670
679
  },
671
680
  async parsePrinciple(options) {
672
681
  return parsePrinciple(fileSystem, dustPath, options.slug);
@@ -677,7 +686,7 @@ function buildArtifactsRepository(fileSystem, dustPath) {
677
686
  return [];
678
687
  }
679
688
  const files = await fileSystem.readdir(principlesPath);
680
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
689
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
681
690
  },
682
691
  async parseFact(options) {
683
692
  return parseFact(fileSystem, dustPath, options.slug);
@@ -688,7 +697,7 @@ function buildArtifactsRepository(fileSystem, dustPath) {
688
697
  return [];
689
698
  }
690
699
  const files = await fileSystem.readdir(factsPath);
691
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
700
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
692
701
  },
693
702
  async parseTask(options) {
694
703
  return parseTask(fileSystem, dustPath, options.slug);
@@ -699,19 +708,7 @@ function buildArtifactsRepository(fileSystem, dustPath) {
699
708
  return [];
700
709
  }
701
710
  const files = await fileSystem.readdir(tasksPath);
702
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
703
- },
704
- async createRefineIdeaTask(options) {
705
- return createRefineIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.openQuestionResponses, options.dustCommand);
706
- },
707
- async createDecomposeIdeaTask(options) {
708
- return decomposeIdea(fileSystem, dustPath, options, options.dustCommand);
709
- },
710
- async createShelveIdeaTask(options) {
711
- return createShelveIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.dustCommand);
712
- },
713
- async createIdeaTask(options) {
714
- return createIdeaTask(fileSystem, dustPath, options);
711
+ return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).toSorted();
715
712
  },
716
713
  async findWorkflowTaskForIdea(options) {
717
714
  return findWorkflowTaskForIdea(fileSystem, dustPath, options.ideaSlug);
@@ -742,84 +739,29 @@ function buildArtifactsRepository(fileSystem, dustPath) {
742
739
  }
743
740
  };
744
741
  }
745
- function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
742
+ function buildArtifactsRepository(fileSystem, dustPath) {
746
743
  return {
747
- artifactPath(type, slug) {
748
- return `${dustPath}/${type}/${slug}.md`;
749
- },
750
- async parseIdea(options) {
751
- return parseIdea(fileSystem, dustPath, options.slug);
752
- },
753
- async listIdeas() {
754
- const ideasPath = `${dustPath}/ideas`;
755
- if (!fileSystem.exists(ideasPath)) {
756
- return [];
757
- }
758
- const files = await fileSystem.readdir(ideasPath);
759
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
760
- },
761
- async parsePrinciple(options) {
762
- return parsePrinciple(fileSystem, dustPath, options.slug);
763
- },
764
- async listPrinciples() {
765
- const principlesPath = `${dustPath}/principles`;
766
- if (!fileSystem.exists(principlesPath)) {
767
- return [];
768
- }
769
- const files = await fileSystem.readdir(principlesPath);
770
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
771
- },
772
- async parseFact(options) {
773
- return parseFact(fileSystem, dustPath, options.slug);
774
- },
775
- async listFacts() {
776
- const factsPath = `${dustPath}/facts`;
777
- if (!fileSystem.exists(factsPath)) {
778
- return [];
779
- }
780
- const files = await fileSystem.readdir(factsPath);
781
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
782
- },
783
- async parseTask(options) {
784
- return parseTask(fileSystem, dustPath, options.slug);
744
+ ...buildReadOperations(fileSystem, dustPath),
745
+ async createRefineIdeaTask(options) {
746
+ return createRefineIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.openQuestionResponses, options.dustCommand);
785
747
  },
786
- async listTasks() {
787
- const tasksPath = `${dustPath}/tasks`;
788
- if (!fileSystem.exists(tasksPath)) {
789
- return [];
790
- }
791
- const files = await fileSystem.readdir(tasksPath);
792
- return files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, "")).sort();
748
+ async createDecomposeIdeaTask(options) {
749
+ return decomposeIdea(fileSystem, dustPath, options, options.dustCommand);
793
750
  },
794
- async findWorkflowTaskForIdea(options) {
795
- return findWorkflowTaskForIdea(fileSystem, dustPath, options.ideaSlug);
751
+ async createShelveIdeaTask(options) {
752
+ return createShelveIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.dustCommand);
796
753
  },
797
- async parseCaptureIdeaTask(options) {
798
- return parseCaptureIdeaTask(fileSystem, dustPath, options.taskSlug);
754
+ async createExpediteIdeaTask(options) {
755
+ return createExpediteIdeaTask(fileSystem, dustPath, options.ideaSlug, options.description, options.dustCommand);
799
756
  },
800
- async buildTaskGraph() {
801
- const taskSlugs = await this.listTasks();
802
- const allWorkflowTasks = await findAllWorkflowTasks(fileSystem, dustPath);
803
- const workflowTypeByTaskSlug = new Map;
804
- for (const match of allWorkflowTasks.workflowTasksByIdeaSlug.values()) {
805
- workflowTypeByTaskSlug.set(match.taskSlug, match.type);
806
- }
807
- const nodes = [];
808
- const edges = [];
809
- for (const slug of taskSlugs) {
810
- const task = await this.parseTask({ slug });
811
- nodes.push({
812
- task,
813
- workflowType: workflowTypeByTaskSlug.get(slug) ?? null
814
- });
815
- for (const blockerSlug of task.blockedBy) {
816
- edges.push({ from: blockerSlug, to: slug });
817
- }
818
- }
819
- return { nodes, edges };
757
+ async createIdeaTask(options) {
758
+ return createIdeaTask(fileSystem, dustPath, options);
820
759
  }
821
760
  };
822
761
  }
762
+ function buildReadOnlyArtifactsRepository(fileSystem, dustPath) {
763
+ return buildReadOperations(fileSystem, dustPath);
764
+ }
823
765
  export {
824
766
  parseResolvedQuestions,
825
767
  parseOpenQuestions,
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Checks Audit - Pure functions for detecting tech stacks and suggesting checks.
3
+ *
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.
7
+ */
8
+ 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
+ }
15
+ export interface CheckOption {
16
+ name: string;
17
+ command: string;
18
+ description?: string;
19
+ }
20
+ export interface CheckSuggestion {
21
+ category: string;
22
+ ecosystem: Ecosystem;
23
+ reason: string;
24
+ suggestedCheck: CheckOption;
25
+ alternatives: CheckOption[];
26
+ detectedIndicators: string[];
27
+ }
28
+ export interface CIFileContent {
29
+ path: string;
30
+ content: string;
31
+ }
32
+ /**
33
+ * Detects tech stacks based on project files.
34
+ */
35
+ export declare function detectTechStack(projectFiles: string[]): TechStackDetection[];
36
+ /**
37
+ * Extracts check categories from existing dust settings.
38
+ */
39
+ export declare function detectConfiguredChecks(settings: DustSettings): Set<string>;
40
+ /**
41
+ * Parses CI configuration files to detect what checks run in CI.
42
+ */
43
+ export declare function detectCIChecks(ciFiles: CIFileContent[]): Set<string>;
44
+ /**
45
+ * Suggests missing checks based on detected tech stack and configured checks.
46
+ */
47
+ export declare function suggestChecks(techStack: TechStackDetection[], projectFiles: string[], configuredChecks: Set<string>, ciChecks: Set<string>): CheckSuggestion[];
48
+ /**
49
+ * Renders a check suggestion as an idea file content.
50
+ */
51
+ export declare function renderCheckIdea(suggestion: CheckSuggestion, packageManager?: string): string;
52
+ /**
53
+ * Returns the checks-audit stock audit template.
54
+ */
55
+ export declare function checksAuditTemplate(): string;