@joshski/dust 0.1.96 → 0.1.97
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/artifacts/ideas.d.ts +17 -0
- package/dist/artifacts/index.d.ts +4 -3
- package/dist/artifacts.js +127 -29
- package/dist/audits.js +72 -38
- package/dist/bucket/repository-lifecycle.d.ts +38 -0
- package/dist/bucket/repository.d.ts +4 -4
- package/dist/dust.js +935 -600
- package/dist/loop/git-pull.d.ts +2 -2
- package/dist/types.d.ts +1 -1
- package/dist/validation.js +154 -101
- package/package.json +1 -1
package/dist/loop/git-pull.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
-
type
|
|
2
|
+
type PullResult = {
|
|
3
3
|
success: true;
|
|
4
4
|
} | {
|
|
5
5
|
success: false;
|
|
6
6
|
message: string;
|
|
7
7
|
};
|
|
8
|
-
export declare function gitPull(cwd: string, spawn: typeof nodeSpawn): Promise<
|
|
8
|
+
export declare function gitPull(cwd: string, spawn: typeof nodeSpawn): Promise<PullResult>;
|
|
9
9
|
export {};
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the event protocol, workflow tasks, and idea structures.
|
|
6
6
|
*/
|
|
7
7
|
export type { AgentSessionEvent, EventMessage } from './agent-events';
|
|
8
|
-
export type { Idea, IdeaOpenQuestion, IdeaOption } from './artifacts/ideas';
|
|
8
|
+
export type { Idea, IdeaOpenQuestion, IdeaOption, ParsedIdeaContent, } from './artifacts/ideas';
|
|
9
9
|
export type { ArtifactType, TaskGraph, TaskGraphNode } from './artifacts/index';
|
|
10
10
|
export type { Task } from './artifacts/tasks';
|
|
11
11
|
export type { CreateIdeaTransitionTaskResult, DecomposeIdeaOptions, IdeaInProgress, OpenQuestionResponse, ParsedCaptureIdeaTask, WorkflowTaskMatch, WorkflowTaskType, } from './artifacts/workflow-tasks';
|
package/dist/validation.js
CHANGED
|
@@ -7,50 +7,68 @@ function extractTitle(content) {
|
|
|
7
7
|
return match ? match[1].trim() : null;
|
|
8
8
|
}
|
|
9
9
|
var MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/;
|
|
10
|
-
function
|
|
11
|
-
const lines = content.split(`
|
|
12
|
-
`);
|
|
13
|
-
let h1Index = -1;
|
|
10
|
+
function findH1Index(lines) {
|
|
14
11
|
for (let i = 0;i < lines.length; i++) {
|
|
15
12
|
if (lines[i].match(/^#\s+.+$/)) {
|
|
16
|
-
|
|
17
|
-
break;
|
|
13
|
+
return i;
|
|
18
14
|
}
|
|
19
15
|
}
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
return -1;
|
|
17
|
+
}
|
|
18
|
+
function findFirstNonBlankLineAfter(lines, startIndex) {
|
|
19
|
+
for (let i = startIndex + 1;i < lines.length; i++) {
|
|
20
|
+
if (lines[i].trim() !== "") {
|
|
21
|
+
return i;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return -1;
|
|
25
|
+
}
|
|
26
|
+
var LIST_ITEM_PREFIXES = ["-", "*", "+"];
|
|
27
|
+
var STRUCTURAL_PREFIXES = ["#", "```", ">"];
|
|
28
|
+
function isStructuralElement(line) {
|
|
29
|
+
if (STRUCTURAL_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
30
|
+
return true;
|
|
22
31
|
}
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
if (LIST_ITEM_PREFIXES.some((prefix) => line.startsWith(prefix))) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return /^\d+\./.test(line);
|
|
36
|
+
}
|
|
37
|
+
function isBlockBreak(line) {
|
|
38
|
+
return STRUCTURAL_PREFIXES.some((prefix) => line.startsWith(prefix));
|
|
39
|
+
}
|
|
40
|
+
function collectParagraph(lines, startIndex) {
|
|
41
|
+
const parts = [];
|
|
42
|
+
for (let i = startIndex;i < lines.length; i++) {
|
|
25
43
|
const line = lines[i].trim();
|
|
26
|
-
if (line
|
|
27
|
-
paragraphStart = i;
|
|
44
|
+
if (line === "" || isBlockBreak(line)) {
|
|
28
45
|
break;
|
|
29
46
|
}
|
|
47
|
+
parts.push(line);
|
|
30
48
|
}
|
|
31
|
-
|
|
49
|
+
return parts.join(" ");
|
|
50
|
+
}
|
|
51
|
+
function extractFirstSentence(paragraph) {
|
|
52
|
+
const match = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
53
|
+
return match ? match[1] : null;
|
|
54
|
+
}
|
|
55
|
+
function extractOpeningSentence(content) {
|
|
56
|
+
const lines = content.split(`
|
|
57
|
+
`);
|
|
58
|
+
const h1Index = findH1Index(lines);
|
|
59
|
+
if (h1Index === -1) {
|
|
32
60
|
return null;
|
|
33
61
|
}
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
if (trimmedFirstLine.startsWith("#") || trimmedFirstLine.startsWith("-") || trimmedFirstLine.startsWith("*") || trimmedFirstLine.startsWith("+") || trimmedFirstLine.match(/^\d+\./) || trimmedFirstLine.startsWith("```") || trimmedFirstLine.startsWith(">")) {
|
|
62
|
+
const paragraphStart = findFirstNonBlankLineAfter(lines, h1Index);
|
|
63
|
+
if (paragraphStart === -1) {
|
|
37
64
|
return null;
|
|
38
65
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const line = lines[i].trim();
|
|
42
|
-
if (line === "")
|
|
43
|
-
break;
|
|
44
|
-
if (line.startsWith("#") || line.startsWith("```") || line.startsWith(">")) {
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
paragraph += (paragraph ? " " : "") + line;
|
|
48
|
-
}
|
|
49
|
-
const sentenceMatch = paragraph.match(/^(.+?[.?!])(?:\s|$)/);
|
|
50
|
-
if (!sentenceMatch) {
|
|
66
|
+
const trimmedFirstLine = lines[paragraphStart].trim();
|
|
67
|
+
if (isStructuralElement(trimmedFirstLine)) {
|
|
51
68
|
return null;
|
|
52
69
|
}
|
|
53
|
-
|
|
70
|
+
const paragraph = collectParagraph(lines, paragraphStart);
|
|
71
|
+
return extractFirstSentence(paragraph);
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
// lib/lint/validators/content-validator.ts
|
|
@@ -208,6 +226,32 @@ var WORKFLOW_PREFIX_TO_SECTION = {
|
|
|
208
226
|
"Shelve Idea: ": "Shelves Idea",
|
|
209
227
|
"Expedite Idea: ": "Expedites Idea"
|
|
210
228
|
};
|
|
229
|
+
function validateH2Heading(filePath, line, lineNumber, inOpenQuestions, currentQuestionLine) {
|
|
230
|
+
const violations = [];
|
|
231
|
+
if (inOpenQuestions && currentQuestionLine !== null) {
|
|
232
|
+
violations.push({
|
|
233
|
+
file: filePath,
|
|
234
|
+
message: "Question has no options listed beneath it",
|
|
235
|
+
line: currentQuestionLine
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (inOpenQuestions && line !== "## Open Questions") {
|
|
239
|
+
violations.push({
|
|
240
|
+
file: filePath,
|
|
241
|
+
message: "Open Questions must be the last section in an idea file. Move this section above ## Open Questions.",
|
|
242
|
+
line: lineNumber
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const headingText = line.slice(3).trimEnd();
|
|
246
|
+
if (headingText.toLowerCase() === "open questions" && headingText !== "Open Questions") {
|
|
247
|
+
violations.push({
|
|
248
|
+
file: filePath,
|
|
249
|
+
message: `Heading "${line.trimEnd()}" should be "## Open Questions"`,
|
|
250
|
+
line: lineNumber
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return violations;
|
|
254
|
+
}
|
|
211
255
|
function validateIdeaOpenQuestions(filePath, content) {
|
|
212
256
|
const violations = [];
|
|
213
257
|
const lines = content.split(`
|
|
@@ -221,22 +265,21 @@ function validateIdeaOpenQuestions(filePath, content) {
|
|
|
221
265
|
const line = lines[i];
|
|
222
266
|
const trimmedLine = line.trimEnd();
|
|
223
267
|
const nonWhitespaceLine = line.trim();
|
|
224
|
-
if (line.startsWith("
|
|
225
|
-
if (
|
|
226
|
-
violations.push({
|
|
227
|
-
file: filePath,
|
|
228
|
-
message: "Question has no options listed beneath it",
|
|
229
|
-
line: currentQuestionLine
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
const headingText = line.slice(3).trimEnd();
|
|
233
|
-
if (headingText.toLowerCase() === "open questions" && headingText !== "Open Questions") {
|
|
268
|
+
if (inOpenQuestions && line.startsWith("```")) {
|
|
269
|
+
if (!inOption && !inCodeBlock) {
|
|
234
270
|
violations.push({
|
|
235
271
|
file: filePath,
|
|
236
|
-
message:
|
|
272
|
+
message: topLevelStructureMessage,
|
|
237
273
|
line: i + 1
|
|
238
274
|
});
|
|
239
275
|
}
|
|
276
|
+
inCodeBlock = !inCodeBlock;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (inCodeBlock)
|
|
280
|
+
continue;
|
|
281
|
+
if (line.startsWith("## ")) {
|
|
282
|
+
violations.push(...validateH2Heading(filePath, line, i + 1, inOpenQuestions, currentQuestionLine));
|
|
240
283
|
inOpenQuestions = line === "## Open Questions";
|
|
241
284
|
currentQuestionLine = null;
|
|
242
285
|
inOption = false;
|
|
@@ -245,19 +288,6 @@ function validateIdeaOpenQuestions(filePath, content) {
|
|
|
245
288
|
}
|
|
246
289
|
if (!inOpenQuestions)
|
|
247
290
|
continue;
|
|
248
|
-
if (line.startsWith("```")) {
|
|
249
|
-
if (!inOption && !inCodeBlock) {
|
|
250
|
-
violations.push({
|
|
251
|
-
file: filePath,
|
|
252
|
-
message: topLevelStructureMessage,
|
|
253
|
-
line: i + 1
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
inCodeBlock = !inCodeBlock;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (inCodeBlock)
|
|
260
|
-
continue;
|
|
261
291
|
if (line.startsWith("### ")) {
|
|
262
292
|
inOption = false;
|
|
263
293
|
if (currentQuestionLine !== null) {
|
|
@@ -825,8 +855,37 @@ function relativizeViolations(violations, cwd) {
|
|
|
825
855
|
file: relativizeViolationFilePath(violation.file, cwd)
|
|
826
856
|
}));
|
|
827
857
|
}
|
|
828
|
-
|
|
829
|
-
|
|
858
|
+
var CONTENT_DIRS = ["principles", "facts", "ideas", "tasks"];
|
|
859
|
+
function validateContentFile(filePath, content) {
|
|
860
|
+
const violations = [];
|
|
861
|
+
const openingSentence = validateOpeningSentence(filePath, content);
|
|
862
|
+
if (openingSentence)
|
|
863
|
+
violations.push(openingSentence);
|
|
864
|
+
const openingSentenceLength = validateOpeningSentenceLength(filePath, content);
|
|
865
|
+
if (openingSentenceLength)
|
|
866
|
+
violations.push(openingSentenceLength);
|
|
867
|
+
const titleFilename = validateTitleFilenameMatch(filePath, content);
|
|
868
|
+
if (titleFilename)
|
|
869
|
+
violations.push(titleFilename);
|
|
870
|
+
return violations;
|
|
871
|
+
}
|
|
872
|
+
function validateTaskFile(filePath, content, ideasPath, overlayFs) {
|
|
873
|
+
const violations = [];
|
|
874
|
+
const filenameViolation = validateFilename(filePath);
|
|
875
|
+
if (filenameViolation)
|
|
876
|
+
violations.push(filenameViolation);
|
|
877
|
+
violations.push(...validateTaskHeadings(filePath, content));
|
|
878
|
+
violations.push(...validateSemanticLinks(filePath, content));
|
|
879
|
+
const imperativeViolation = validateImperativeOpeningSentence(filePath, content);
|
|
880
|
+
if (imperativeViolation)
|
|
881
|
+
violations.push(imperativeViolation);
|
|
882
|
+
const ideaTransition = validateIdeaTransitionTitle(filePath, content, ideasPath, overlayFs);
|
|
883
|
+
if (ideaTransition)
|
|
884
|
+
violations.push(ideaTransition);
|
|
885
|
+
violations.push(...validateWorkflowTaskBodySection(filePath, content, ideasPath, overlayFs));
|
|
886
|
+
return violations;
|
|
887
|
+
}
|
|
888
|
+
function parsePatchFiles(dustPath, patch) {
|
|
830
889
|
const absolutePatchFiles = new Map;
|
|
831
890
|
const deletedPaths = new Set;
|
|
832
891
|
for (const [relativePath, content] of Object.entries(patch.files)) {
|
|
@@ -837,20 +896,51 @@ async function validatePatch(fileSystem, dustPath, patch, options = {}) {
|
|
|
837
896
|
absolutePatchFiles.set(absolutePath, content);
|
|
838
897
|
}
|
|
839
898
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const contentDirs = ["principles", "facts", "ideas", "tasks"];
|
|
899
|
+
return { absolutePatchFiles, deletedPaths };
|
|
900
|
+
}
|
|
901
|
+
function collectPatchDirs(patch) {
|
|
844
902
|
const patchDirs = new Set;
|
|
845
903
|
for (const relativePath of Object.keys(patch.files)) {
|
|
846
904
|
const dir = relativePath.split("/")[0];
|
|
847
|
-
if (
|
|
905
|
+
if (CONTENT_DIRS.includes(dir)) {
|
|
848
906
|
patchDirs.add(dir);
|
|
849
907
|
}
|
|
850
908
|
}
|
|
909
|
+
return patchDirs;
|
|
910
|
+
}
|
|
911
|
+
async function validatePrincipleRelationships(dustPath, overlayFs) {
|
|
912
|
+
const allRelationships = [];
|
|
913
|
+
const principlesPath = `${dustPath}/principles`;
|
|
914
|
+
try {
|
|
915
|
+
const existingFiles = await overlayFs.readdir(principlesPath);
|
|
916
|
+
for (const file of existingFiles) {
|
|
917
|
+
if (!file.endsWith(".md"))
|
|
918
|
+
continue;
|
|
919
|
+
const filePath = `${principlesPath}/${file}`;
|
|
920
|
+
const content = await overlayFs.readFile(filePath);
|
|
921
|
+
allRelationships.push(extractPrincipleRelationships(filePath, content));
|
|
922
|
+
}
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (error.code !== "ENOENT") {
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return [
|
|
929
|
+
...validateBidirectionalLinks(allRelationships),
|
|
930
|
+
...validateNoCycles(allRelationships)
|
|
931
|
+
];
|
|
932
|
+
}
|
|
933
|
+
async function validatePatch(fileSystem, dustPath, patch, options = {}) {
|
|
934
|
+
const cwd = options.cwd ?? process.cwd();
|
|
935
|
+
const { absolutePatchFiles, deletedPaths } = parsePatchFiles(dustPath, patch);
|
|
936
|
+
const overlayFs = createOverlayFileSystem(fileSystem, absolutePatchFiles, deletedPaths);
|
|
937
|
+
const violations = [];
|
|
938
|
+
violations.push(...validatePatchRootEntries(fileSystem, dustPath, patch));
|
|
939
|
+
const patchDirs = collectPatchDirs(patch);
|
|
851
940
|
for (const dir of patchDirs) {
|
|
852
941
|
violations.push(...await validateContentDirectoryFiles(`${dustPath}/${dir}`, overlayFs));
|
|
853
942
|
}
|
|
943
|
+
const ideasPath = `${dustPath}/ideas`;
|
|
854
944
|
for (const [relativePath, content] of Object.entries(patch.files)) {
|
|
855
945
|
if (content === null)
|
|
856
946
|
continue;
|
|
@@ -859,34 +949,14 @@ async function validatePatch(fileSystem, dustPath, patch, options = {}) {
|
|
|
859
949
|
const filePath = `${dustPath}/${relativePath}`;
|
|
860
950
|
const dir = relativePath.split("/")[0];
|
|
861
951
|
violations.push(...validateLinks(filePath, content, overlayFs));
|
|
862
|
-
if (
|
|
863
|
-
|
|
864
|
-
if (openingSentence)
|
|
865
|
-
violations.push(openingSentence);
|
|
866
|
-
const openingSentenceLength = validateOpeningSentenceLength(filePath, content);
|
|
867
|
-
if (openingSentenceLength)
|
|
868
|
-
violations.push(openingSentenceLength);
|
|
869
|
-
const titleFilename = validateTitleFilenameMatch(filePath, content);
|
|
870
|
-
if (titleFilename)
|
|
871
|
-
violations.push(titleFilename);
|
|
952
|
+
if (CONTENT_DIRS.includes(dir)) {
|
|
953
|
+
violations.push(...validateContentFile(filePath, content));
|
|
872
954
|
}
|
|
873
955
|
if (dir === "ideas") {
|
|
874
956
|
violations.push(...validateIdeaOpenQuestions(filePath, content));
|
|
875
957
|
}
|
|
876
958
|
if (dir === "tasks") {
|
|
877
|
-
|
|
878
|
-
if (filenameViolation)
|
|
879
|
-
violations.push(filenameViolation);
|
|
880
|
-
violations.push(...validateTaskHeadings(filePath, content));
|
|
881
|
-
violations.push(...validateSemanticLinks(filePath, content));
|
|
882
|
-
const imperativeViolation = validateImperativeOpeningSentence(filePath, content);
|
|
883
|
-
if (imperativeViolation)
|
|
884
|
-
violations.push(imperativeViolation);
|
|
885
|
-
const ideasPath = `${dustPath}/ideas`;
|
|
886
|
-
const ideaTransition = validateIdeaTransitionTitle(filePath, content, ideasPath, overlayFs);
|
|
887
|
-
if (ideaTransition)
|
|
888
|
-
violations.push(ideaTransition);
|
|
889
|
-
violations.push(...validateWorkflowTaskBodySection(filePath, content, ideasPath, overlayFs));
|
|
959
|
+
violations.push(...validateTaskFile(filePath, content, ideasPath, overlayFs));
|
|
890
960
|
}
|
|
891
961
|
if (dir === "principles") {
|
|
892
962
|
violations.push(...validatePrincipleHierarchySections(filePath, content));
|
|
@@ -895,24 +965,7 @@ async function validatePatch(fileSystem, dustPath, patch, options = {}) {
|
|
|
895
965
|
}
|
|
896
966
|
const hasPrinciplePatches = Object.keys(patch.files).some((p) => p.startsWith("principles/"));
|
|
897
967
|
if (hasPrinciplePatches) {
|
|
898
|
-
|
|
899
|
-
const principlesPath = `${dustPath}/principles`;
|
|
900
|
-
try {
|
|
901
|
-
const existingFiles = await overlayFs.readdir(principlesPath);
|
|
902
|
-
for (const file of existingFiles) {
|
|
903
|
-
if (!file.endsWith(".md"))
|
|
904
|
-
continue;
|
|
905
|
-
const filePath = `${principlesPath}/${file}`;
|
|
906
|
-
const content = await overlayFs.readFile(filePath);
|
|
907
|
-
allRelationships.push(extractPrincipleRelationships(filePath, content));
|
|
908
|
-
}
|
|
909
|
-
} catch (error) {
|
|
910
|
-
if (error.code !== "ENOENT") {
|
|
911
|
-
throw error;
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
violations.push(...validateBidirectionalLinks(allRelationships));
|
|
915
|
-
violations.push(...validateNoCycles(allRelationships));
|
|
968
|
+
violations.push(...await validatePrincipleRelationships(dustPath, overlayFs));
|
|
916
969
|
}
|
|
917
970
|
return {
|
|
918
971
|
valid: violations.length === 0,
|