@prowi/deskcheck 0.1.0
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/LICENSE +21 -0
- package/README.md +266 -0
- package/build/agents/executor-prompt.d.ts +10 -0
- package/build/agents/executor-prompt.d.ts.map +1 -0
- package/build/agents/executor-prompt.js +65 -0
- package/build/agents/executor-prompt.js.map +1 -0
- package/build/agents/orchestrator.d.ts +52 -0
- package/build/agents/orchestrator.d.ts.map +1 -0
- package/build/agents/orchestrator.js +343 -0
- package/build/agents/orchestrator.js.map +1 -0
- package/build/agents/planner.d.ts +28 -0
- package/build/agents/planner.d.ts.map +1 -0
- package/build/agents/planner.js +138 -0
- package/build/agents/planner.js.map +1 -0
- package/build/cli.d.ts +3 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +467 -0
- package/build/cli.js.map +1 -0
- package/build/core/config.d.ts +16 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +81 -0
- package/build/core/config.js.map +1 -0
- package/build/core/context-extractor.d.ts +17 -0
- package/build/core/context-extractor.d.ts.map +1 -0
- package/build/core/context-extractor.js +69 -0
- package/build/core/context-extractor.js.map +1 -0
- package/build/core/glob-matcher.d.ts +32 -0
- package/build/core/glob-matcher.d.ts.map +1 -0
- package/build/core/glob-matcher.js +51 -0
- package/build/core/glob-matcher.js.map +1 -0
- package/build/core/module-parser.d.ts +26 -0
- package/build/core/module-parser.d.ts.map +1 -0
- package/build/core/module-parser.js +98 -0
- package/build/core/module-parser.js.map +1 -0
- package/build/core/plan-builder.d.ts +12 -0
- package/build/core/plan-builder.d.ts.map +1 -0
- package/build/core/plan-builder.js +66 -0
- package/build/core/plan-builder.js.map +1 -0
- package/build/core/storage.d.ts +118 -0
- package/build/core/storage.d.ts.map +1 -0
- package/build/core/storage.js +590 -0
- package/build/core/storage.js.map +1 -0
- package/build/core/types.d.ts +268 -0
- package/build/core/types.d.ts.map +1 -0
- package/build/core/types.js +5 -0
- package/build/core/types.js.map +1 -0
- package/build/mcp/tools.d.ts +10 -0
- package/build/mcp/tools.d.ts.map +1 -0
- package/build/mcp/tools.js +354 -0
- package/build/mcp/tools.js.map +1 -0
- package/build/mcp-server.d.ts +3 -0
- package/build/mcp-server.d.ts.map +1 -0
- package/build/mcp-server.js +15 -0
- package/build/mcp-server.js.map +1 -0
- package/build/renderers/json.d.ts +4 -0
- package/build/renderers/json.d.ts.map +1 -0
- package/build/renderers/json.js +5 -0
- package/build/renderers/json.js.map +1 -0
- package/build/renderers/markdown.d.ts +4 -0
- package/build/renderers/markdown.d.ts.map +1 -0
- package/build/renderers/markdown.js +36 -0
- package/build/renderers/markdown.js.map +1 -0
- package/build/renderers/shared.d.ts +23 -0
- package/build/renderers/shared.d.ts.map +1 -0
- package/build/renderers/shared.js +30 -0
- package/build/renderers/shared.js.map +1 -0
- package/build/renderers/terminal.d.ts +4 -0
- package/build/renderers/terminal.d.ts.map +1 -0
- package/build/renderers/terminal.js +88 -0
- package/build/renderers/terminal.js.map +1 -0
- package/build/renderers/watch.d.ts +4 -0
- package/build/renderers/watch.d.ts.map +1 -0
- package/build/renderers/watch.js +119 -0
- package/build/renderers/watch.js.map +1 -0
- package/build/serve.d.ts +9 -0
- package/build/serve.d.ts.map +1 -0
- package/build/serve.js +249 -0
- package/build/serve.js.map +1 -0
- package/package.json +41 -0
- package/ui/dist/index.html +92 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
/**
|
|
5
|
+
* Extract review context based on the source type.
|
|
6
|
+
*
|
|
7
|
+
* - diff: runs `git diff` for the given files against the source target
|
|
8
|
+
* - file: reads each file's content from disk
|
|
9
|
+
* - symbol: reads the file containing the symbol (symbol identification
|
|
10
|
+
* is left to the executor agent which has Read/Grep tools)
|
|
11
|
+
*/
|
|
12
|
+
export function extractContext(sourceType, sourceTarget, files, projectRoot, symbol) {
|
|
13
|
+
let content;
|
|
14
|
+
switch (sourceType) {
|
|
15
|
+
case "diff":
|
|
16
|
+
content = extractDiffContext(sourceTarget, files, projectRoot);
|
|
17
|
+
break;
|
|
18
|
+
case "file":
|
|
19
|
+
content = extractFileContext(files, projectRoot);
|
|
20
|
+
break;
|
|
21
|
+
case "symbol":
|
|
22
|
+
content = extractFileContext(files, projectRoot);
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
throw new Error(`Unknown source type: ${sourceType}`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
contextType: sourceType,
|
|
29
|
+
content,
|
|
30
|
+
symbol: symbol ?? null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Run git diff for each file and concatenate results. */
|
|
34
|
+
function extractDiffContext(target, files, projectRoot) {
|
|
35
|
+
const parts = [];
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
try {
|
|
38
|
+
const diff = execFileSync("git", ["diff", target, "--", file], {
|
|
39
|
+
cwd: projectRoot,
|
|
40
|
+
encoding: "utf-8",
|
|
41
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
42
|
+
timeout: 30_000,
|
|
43
|
+
});
|
|
44
|
+
if (diff.trim().length > 0) {
|
|
45
|
+
parts.push(diff);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`Warning: git diff failed for ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return parts.join("\n");
|
|
53
|
+
}
|
|
54
|
+
/** Read each file's content and concatenate with file path headers. */
|
|
55
|
+
function extractFileContext(files, projectRoot) {
|
|
56
|
+
const parts = [];
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
try {
|
|
59
|
+
const absolutePath = path.resolve(projectRoot, file);
|
|
60
|
+
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
61
|
+
parts.push(`--- ${file} ---\n${content}`);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
console.error(`Warning: could not read ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return parts.join("\n\n");
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=context-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-extractor.js","sourceRoot":"","sources":["../../src/core/context-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAuB,EACvB,YAAoB,EACpB,KAAe,EACf,WAAmB,EACnB,MAAe;IAEf,IAAI,OAAe,CAAC;IAEpB,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,GAAG,kBAAkB,CAAC,YAAY,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;YAC/D,MAAM;QACR,KAAK,MAAM;YACT,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YACjD,MAAM;QACR,KAAK,QAAQ;YACX,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YACjD,MAAM;QACR;YACE,MAAM,IAAI,KAAK,CAAC,wBAAwB,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO;QACL,WAAW,EAAE,UAAU;QACvB,OAAO;QACP,MAAM,EAAE,MAAM,IAAI,IAAI;KACvB,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,SAAS,kBAAkB,CACzB,MAAc,EACd,KAAe,EACf,WAAmB;IAEnB,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;gBAC7D,GAAG,EAAE,WAAW;gBAChB,QAAQ,EAAE,OAAO;gBACjB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;gBAC3B,OAAO,EAAE,MAAM;aAChB,CAAC,CAAC;YAEH,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,gCAAgC,IAAI,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAClG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,uEAAuE;AACvE,SAAS,kBAAkB,CACzB,KAAe,EACf,WAAmB;IAEnB,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACrD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;YACvD,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,SAAS,OAAO,EAAE,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CACX,2BAA2B,IAAI,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ReviewModule } from "./types.js";
|
|
2
|
+
/** The result of matching changed files against a single criterion. */
|
|
3
|
+
export interface MatchResult {
|
|
4
|
+
/** The criterion that matched. */
|
|
5
|
+
module: ReviewModule;
|
|
6
|
+
/** Files from the changed set that matched the criterion's globs. */
|
|
7
|
+
matchedFiles: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Check whether a single file path matches a set of glob patterns.
|
|
11
|
+
*
|
|
12
|
+
* Globs prefixed with `!` act as exclusion patterns: if a file matches any
|
|
13
|
+
* exclusion glob, it is NOT considered a match even if it matches a positive glob.
|
|
14
|
+
* Positive globs are checked first; then exclusions are applied to remove matches.
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - The file path to test (e.g. "app/Http/Controllers/OrderController.php").
|
|
17
|
+
* @param globs - Array of glob patterns. Prefix with `!` to exclude.
|
|
18
|
+
* @returns True if the file matches at least one positive glob and no exclusion globs.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fileMatchesGlobs(filePath: string, globs: string[]): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Find which criteria match a set of changed files.
|
|
23
|
+
*
|
|
24
|
+
* For each criterion, checks every changed file against the criterion's glob patterns.
|
|
25
|
+
* Criteria with at least one matching file are included in the results.
|
|
26
|
+
*
|
|
27
|
+
* @param changedFiles - Array of file paths that were changed.
|
|
28
|
+
* @param modules - Array of criteria to match against.
|
|
29
|
+
* @returns Array of match results, one per criterion that matched at least one file.
|
|
30
|
+
*/
|
|
31
|
+
export declare function findMatchingModules(changedFiles: string[], modules: ReviewModule[]): MatchResult[];
|
|
32
|
+
//# sourceMappingURL=glob-matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-matcher.d.ts","sourceRoot":"","sources":["../../src/core/glob-matcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,qEAAqE;IACrE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAyB3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EAAE,EACtB,OAAO,EAAE,YAAY,EAAE,GACtB,WAAW,EAAE,CAcf"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { minimatch } from "minimatch";
|
|
2
|
+
/**
|
|
3
|
+
* Check whether a single file path matches a set of glob patterns.
|
|
4
|
+
*
|
|
5
|
+
* Globs prefixed with `!` act as exclusion patterns: if a file matches any
|
|
6
|
+
* exclusion glob, it is NOT considered a match even if it matches a positive glob.
|
|
7
|
+
* Positive globs are checked first; then exclusions are applied to remove matches.
|
|
8
|
+
*
|
|
9
|
+
* @param filePath - The file path to test (e.g. "app/Http/Controllers/OrderController.php").
|
|
10
|
+
* @param globs - Array of glob patterns. Prefix with `!` to exclude.
|
|
11
|
+
* @returns True if the file matches at least one positive glob and no exclusion globs.
|
|
12
|
+
*/
|
|
13
|
+
export function fileMatchesGlobs(filePath, globs) {
|
|
14
|
+
const positiveGlobs = [];
|
|
15
|
+
const exclusionGlobs = [];
|
|
16
|
+
for (const glob of globs) {
|
|
17
|
+
if (glob.startsWith("!")) {
|
|
18
|
+
exclusionGlobs.push(glob.slice(1));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
positiveGlobs.push(glob);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const matchesPositive = positiveGlobs.some((pattern) => minimatch(filePath, pattern));
|
|
25
|
+
if (!matchesPositive) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const matchesExclusion = exclusionGlobs.some((pattern) => minimatch(filePath, pattern));
|
|
29
|
+
return !matchesExclusion;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Find which criteria match a set of changed files.
|
|
33
|
+
*
|
|
34
|
+
* For each criterion, checks every changed file against the criterion's glob patterns.
|
|
35
|
+
* Criteria with at least one matching file are included in the results.
|
|
36
|
+
*
|
|
37
|
+
* @param changedFiles - Array of file paths that were changed.
|
|
38
|
+
* @param modules - Array of criteria to match against.
|
|
39
|
+
* @returns Array of match results, one per criterion that matched at least one file.
|
|
40
|
+
*/
|
|
41
|
+
export function findMatchingModules(changedFiles, modules) {
|
|
42
|
+
const results = [];
|
|
43
|
+
for (const module of modules) {
|
|
44
|
+
const matchedFiles = changedFiles.filter((file) => fileMatchesGlobs(file, module.globs));
|
|
45
|
+
if (matchedFiles.length > 0) {
|
|
46
|
+
results.push({ module, matchedFiles });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=glob-matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-matcher.js","sourceRoot":"","sources":["../../src/core/glob-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAWtC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,KAAe;IAChE,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC;aAAM,CAAC;YACN,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,MAAM,eAAe,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACrD,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAC7B,CAAC;IAEF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACvD,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAC7B,CAAC;IAEF,OAAO,CAAC,gBAAgB,CAAC;AAC3B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAsB,EACtB,OAAuB;IAEvB,MAAM,OAAO,GAAkB,EAAE,CAAC;IAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAChD,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CACrC,CAAC;QAEF,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReviewModule } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a single criterion markdown file into a ReviewModule object.
|
|
4
|
+
*
|
|
5
|
+
* Reads the file, extracts YAML frontmatter with `gray-matter`, validates
|
|
6
|
+
* required fields, applies defaults for optional fields, and returns the
|
|
7
|
+
* structured criterion representation.
|
|
8
|
+
*
|
|
9
|
+
* @param filePath - Absolute or relative path to the markdown file.
|
|
10
|
+
* @param basePath - Base directory used to compute relative paths and criterion IDs.
|
|
11
|
+
* @returns Parsed ReviewModule.
|
|
12
|
+
* @throws {Error} If the file cannot be read or frontmatter is invalid.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseModule(filePath: string, basePath: string): ReviewModule;
|
|
15
|
+
/**
|
|
16
|
+
* Discover all criteria by recursively scanning a directory for `.md` files.
|
|
17
|
+
*
|
|
18
|
+
* Each markdown file is parsed for YAML frontmatter containing criterion metadata
|
|
19
|
+
* (description, severity, globs, etc.) and the markdown body becomes the detective prompt.
|
|
20
|
+
*
|
|
21
|
+
* @param modulesDir - Path to the directory containing criterion markdown files.
|
|
22
|
+
* @returns Array of parsed ReviewModule objects, sorted by ID for deterministic ordering.
|
|
23
|
+
* @throws {Error} If the directory does not exist or any criterion file has invalid frontmatter.
|
|
24
|
+
*/
|
|
25
|
+
export declare function discoverModules(modulesDir: string): ReviewModule[];
|
|
26
|
+
//# sourceMappingURL=module-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-parser.d.ts","sourceRoot":"","sources":["../../src/core/module-parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA8B,YAAY,EAAE,MAAM,YAAY,CAAC;AAkB3E;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,CAkE5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,EAAE,CAqBlE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
const VALID_SEVERITIES = new Set([
|
|
5
|
+
"critical",
|
|
6
|
+
"high",
|
|
7
|
+
"medium",
|
|
8
|
+
"low",
|
|
9
|
+
]);
|
|
10
|
+
const VALID_MODELS = new Set([
|
|
11
|
+
"haiku",
|
|
12
|
+
"sonnet",
|
|
13
|
+
"opus",
|
|
14
|
+
]);
|
|
15
|
+
const DEFAULT_MODE = "Create one review per changed file";
|
|
16
|
+
const DEFAULT_MODEL = "haiku";
|
|
17
|
+
/**
|
|
18
|
+
* Parse a single criterion markdown file into a ReviewModule object.
|
|
19
|
+
*
|
|
20
|
+
* Reads the file, extracts YAML frontmatter with `gray-matter`, validates
|
|
21
|
+
* required fields, applies defaults for optional fields, and returns the
|
|
22
|
+
* structured criterion representation.
|
|
23
|
+
*
|
|
24
|
+
* @param filePath - Absolute or relative path to the markdown file.
|
|
25
|
+
* @param basePath - Base directory used to compute relative paths and criterion IDs.
|
|
26
|
+
* @returns Parsed ReviewModule.
|
|
27
|
+
* @throws {Error} If the file cannot be read or frontmatter is invalid.
|
|
28
|
+
*/
|
|
29
|
+
export function parseModule(filePath, basePath) {
|
|
30
|
+
const absolutePath = path.resolve(filePath);
|
|
31
|
+
const absoluteBase = path.resolve(basePath);
|
|
32
|
+
const raw = fs.readFileSync(absolutePath, "utf-8");
|
|
33
|
+
const { data: frontmatter, content } = matter(raw);
|
|
34
|
+
const relativePath = path.relative(absoluteBase, absolutePath);
|
|
35
|
+
const relativeFile = path.relative(path.dirname(absoluteBase), absolutePath);
|
|
36
|
+
// --- Validate required fields ---
|
|
37
|
+
if (typeof frontmatter.description !== "string" || frontmatter.description.trim() === "") {
|
|
38
|
+
throw new Error(`Invalid criterion ${relativePath}: "description" is required and must be a non-empty string`);
|
|
39
|
+
}
|
|
40
|
+
if (!VALID_SEVERITIES.has(frontmatter.severity)) {
|
|
41
|
+
throw new Error(`Invalid criterion ${relativePath}: "severity" must be one of: ${[...VALID_SEVERITIES].join(", ")}. Got: ${JSON.stringify(frontmatter.severity)}`);
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(frontmatter.globs) || frontmatter.globs.length === 0) {
|
|
44
|
+
throw new Error(`Invalid criterion ${relativePath}: "globs" is required and must be a non-empty array of strings`);
|
|
45
|
+
}
|
|
46
|
+
for (const glob of frontmatter.globs) {
|
|
47
|
+
if (typeof glob !== "string" || glob.trim() === "") {
|
|
48
|
+
throw new Error(`Invalid criterion ${relativePath}: each entry in "globs" must be a non-empty string. Got: ${JSON.stringify(glob)}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// --- Apply defaults for optional fields ---
|
|
52
|
+
const mode = typeof frontmatter.mode === "string" && frontmatter.mode.trim() !== ""
|
|
53
|
+
? frontmatter.mode
|
|
54
|
+
: DEFAULT_MODE;
|
|
55
|
+
const model = frontmatter.model ?? DEFAULT_MODEL;
|
|
56
|
+
if (!VALID_MODELS.has(model)) {
|
|
57
|
+
throw new Error(`Invalid criterion ${relativePath}: "model" must be one of: ${[...VALID_MODELS].join(", ")}. Got: ${JSON.stringify(model)}`);
|
|
58
|
+
}
|
|
59
|
+
// --- Build the criterion ID from relative path without extension ---
|
|
60
|
+
const id = relativePath.replace(/\.md$/, "").split(path.sep).join("/");
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
file: relativeFile.split(path.sep).join("/"),
|
|
64
|
+
description: frontmatter.description,
|
|
65
|
+
severity: frontmatter.severity,
|
|
66
|
+
globs: frontmatter.globs,
|
|
67
|
+
mode,
|
|
68
|
+
model: model,
|
|
69
|
+
prompt: content.trim(),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Discover all criteria by recursively scanning a directory for `.md` files.
|
|
74
|
+
*
|
|
75
|
+
* Each markdown file is parsed for YAML frontmatter containing criterion metadata
|
|
76
|
+
* (description, severity, globs, etc.) and the markdown body becomes the detective prompt.
|
|
77
|
+
*
|
|
78
|
+
* @param modulesDir - Path to the directory containing criterion markdown files.
|
|
79
|
+
* @returns Array of parsed ReviewModule objects, sorted by ID for deterministic ordering.
|
|
80
|
+
* @throws {Error} If the directory does not exist or any criterion file has invalid frontmatter.
|
|
81
|
+
*/
|
|
82
|
+
export function discoverModules(modulesDir) {
|
|
83
|
+
const absoluteDir = path.resolve(modulesDir);
|
|
84
|
+
if (!fs.existsSync(absoluteDir)) {
|
|
85
|
+
throw new Error(`Criteria directory does not exist: ${absoluteDir}`);
|
|
86
|
+
}
|
|
87
|
+
const entries = fs.readdirSync(absoluteDir, { recursive: true });
|
|
88
|
+
const mdFiles = entries
|
|
89
|
+
.filter((entry) => entry.endsWith(".md"))
|
|
90
|
+
.sort();
|
|
91
|
+
const modules = [];
|
|
92
|
+
for (const relativeFile of mdFiles) {
|
|
93
|
+
const absoluteFile = path.join(absoluteDir, relativeFile);
|
|
94
|
+
modules.push(parseModule(absoluteFile, absoluteDir));
|
|
95
|
+
}
|
|
96
|
+
return modules.sort((a, b) => a.id.localeCompare(b.id));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=module-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module-parser.js","sourceRoot":"","sources":["../../src/core/module-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ReviewStorage } from "./storage.js";
|
|
2
|
+
import type { ReviewModule, ReviewPlan, ReviewSource } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a complete review plan: create the plan, match files to modules,
|
|
5
|
+
* generate tasks, and finalize.
|
|
6
|
+
*
|
|
7
|
+
* This is the single authoritative implementation of plan+task creation,
|
|
8
|
+
* shared by both the planner agent's in-process MCP tool and any other
|
|
9
|
+
* entry point that needs to create a plan from a file list.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildPlanWithTasks(storage: ReviewStorage, name: string, source: ReviewSource, files: string[], modules: ReviewModule[]): ReviewPlan;
|
|
12
|
+
//# sourceMappingURL=plan-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-builder.d.ts","sourceRoot":"","sources":["../../src/core/plan-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAEV,YAAY,EACZ,UAAU,EACV,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,EAAE,YAAY,EAAE,GACtB,UAAU,CA6DZ"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { findMatchingModules } from "./glob-matcher.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a complete review plan: create the plan, match files to modules,
|
|
4
|
+
* generate tasks, and finalize.
|
|
5
|
+
*
|
|
6
|
+
* This is the single authoritative implementation of plan+task creation,
|
|
7
|
+
* shared by both the planner agent's in-process MCP tool and any other
|
|
8
|
+
* entry point that needs to create a plan from a file list.
|
|
9
|
+
*/
|
|
10
|
+
export function buildPlanWithTasks(storage, name, source, files, modules) {
|
|
11
|
+
// Create the plan shell
|
|
12
|
+
const plan = storage.createPlan(name, source);
|
|
13
|
+
// Match files against criteria
|
|
14
|
+
const matches = findMatchingModules(files, modules);
|
|
15
|
+
// Track coverage
|
|
16
|
+
const matchedFileSet = new Set();
|
|
17
|
+
for (const match of matches) {
|
|
18
|
+
for (const file of match.matchedFiles) {
|
|
19
|
+
matchedFileSet.add(file);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const matchedFiles = [...matchedFileSet].sort();
|
|
23
|
+
const unmatchedFiles = files.filter((f) => !matchedFileSet.has(f)).sort();
|
|
24
|
+
storage.setMatchedFiles(plan.plan_id, matchedFiles, unmatchedFiles);
|
|
25
|
+
// Set module summaries
|
|
26
|
+
const moduleSummaries = {};
|
|
27
|
+
for (const match of matches) {
|
|
28
|
+
moduleSummaries[match.module.id] = {
|
|
29
|
+
review_id: match.module.id,
|
|
30
|
+
description: match.module.description,
|
|
31
|
+
severity: match.module.severity,
|
|
32
|
+
model: match.module.model,
|
|
33
|
+
task_count: 0,
|
|
34
|
+
matched_files: match.matchedFiles,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
storage.setModules(plan.plan_id, moduleSummaries);
|
|
38
|
+
// Create tasks based on module mode
|
|
39
|
+
for (const match of matches) {
|
|
40
|
+
const isGrouped = match.module.mode.toLowerCase().includes("grouped") ||
|
|
41
|
+
match.module.mode.toLowerCase().includes("all files");
|
|
42
|
+
if (isGrouped) {
|
|
43
|
+
storage.addTask(plan.plan_id, {
|
|
44
|
+
review_id: match.module.id,
|
|
45
|
+
review_file: match.module.file,
|
|
46
|
+
files: match.matchedFiles,
|
|
47
|
+
hint: null,
|
|
48
|
+
model: match.module.model,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
for (const file of match.matchedFiles) {
|
|
53
|
+
storage.addTask(plan.plan_id, {
|
|
54
|
+
review_id: match.module.id,
|
|
55
|
+
review_file: match.module.file,
|
|
56
|
+
files: [file],
|
|
57
|
+
hint: null,
|
|
58
|
+
model: match.module.model,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Finalize the plan (sets status to "ready", recounts tasks per module)
|
|
64
|
+
return storage.finalizePlan(plan.plan_id);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=plan-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-builder.js","sourceRoot":"","sources":["../../src/core/plan-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { ContextType, Finding, ModuleSummary, ReviewPlan, ReviewResults, ReviewSource, ReviewTask, TaskUsage } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Manages the two-file storage format (plan.json + results.json) for deskcheck runs.
|
|
4
|
+
*
|
|
5
|
+
* Each deskcheck run lives in a timestamped directory under the configured storage
|
|
6
|
+
* directory. The plan file tracks tasks and coverage; the results file tracks
|
|
7
|
+
* findings and aggregations.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ReviewStorage {
|
|
10
|
+
private readonly storageDir;
|
|
11
|
+
/** Tracks active lock file paths so they can be cleaned up on process exit. */
|
|
12
|
+
private static activeLocks;
|
|
13
|
+
constructor(storageDir: string);
|
|
14
|
+
/**
|
|
15
|
+
* Execute a function while holding a file-based lock for the given plan.
|
|
16
|
+
*
|
|
17
|
+
* Uses `{ flag: "wx" }` for atomic exclusive create — either the file is
|
|
18
|
+
* created (lock acquired) or it already exists (another holder). This
|
|
19
|
+
* eliminates the TOCTOU race of existsSync + writeFileSync.
|
|
20
|
+
*
|
|
21
|
+
* Stale locks are detected via a timestamp in the lock file body.
|
|
22
|
+
*/
|
|
23
|
+
private withLock;
|
|
24
|
+
/** Get the directory path for a plan. */
|
|
25
|
+
private planDir;
|
|
26
|
+
/**
|
|
27
|
+
* Create a new review plan with an empty task set.
|
|
28
|
+
*
|
|
29
|
+
* Creates the timestamped directory and writes an initial plan.json with
|
|
30
|
+
* status "planning" and empty collections.
|
|
31
|
+
*/
|
|
32
|
+
createPlan(name: string, source: ReviewSource): ReviewPlan;
|
|
33
|
+
/** Read the plan.json for a given plan ID. */
|
|
34
|
+
getPlan(planId: string): ReviewPlan;
|
|
35
|
+
/**
|
|
36
|
+
* Return the most recent plan ID (latest timestamped directory), or null if
|
|
37
|
+
* no plans exist.
|
|
38
|
+
*/
|
|
39
|
+
getLatestPlanId(): string | null;
|
|
40
|
+
/** List all plans with basic metadata. */
|
|
41
|
+
listPlans(): Array<{
|
|
42
|
+
planId: string;
|
|
43
|
+
name: string;
|
|
44
|
+
status: string;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Finalize a plan by setting its status to "ready" and updating task counts
|
|
49
|
+
* in module summaries.
|
|
50
|
+
*/
|
|
51
|
+
finalizePlan(planId: string): ReviewPlan;
|
|
52
|
+
/**
|
|
53
|
+
* Add a task to an existing plan.
|
|
54
|
+
*
|
|
55
|
+
* Auto-generates the task_id by flattening slashes in the review_id and
|
|
56
|
+
* appending a zero-padded incrementing suffix (e.g., "-001", "-002").
|
|
57
|
+
* The task is created with status "pending" and null context fields.
|
|
58
|
+
*/
|
|
59
|
+
addTask(planId: string, task: Omit<ReviewTask, "task_id" | "status" | "created_at" | "started_at" | "completed_at" | "context" | "context_type" | "symbol" | "prompt">): ReviewTask;
|
|
60
|
+
/**
|
|
61
|
+
* Claim a pending task for execution.
|
|
62
|
+
*
|
|
63
|
+
* Sets the task status to "in_progress", fills in the context fields
|
|
64
|
+
* (context type, content, symbol, prompt), and records the start time.
|
|
65
|
+
*/
|
|
66
|
+
claimTask(planId: string, taskId: string, context: {
|
|
67
|
+
contextType: ContextType;
|
|
68
|
+
content: string;
|
|
69
|
+
symbol?: string;
|
|
70
|
+
prompt: string;
|
|
71
|
+
}): ReviewTask;
|
|
72
|
+
/**
|
|
73
|
+
* Return tasks eligible for execution: those with status "pending" or
|
|
74
|
+
* "in_progress" tasks that are stale (older than 5 minutes).
|
|
75
|
+
*/
|
|
76
|
+
getPendingTasks(planId: string): ReviewTask[];
|
|
77
|
+
/**
|
|
78
|
+
* Mark a task as complete and record its findings.
|
|
79
|
+
*
|
|
80
|
+
* Updates the task status in plan.json, adds a TaskResult entry to
|
|
81
|
+
* results.json, and recomputes all aggregations (by_file, by_module,
|
|
82
|
+
* summary, completion).
|
|
83
|
+
*/
|
|
84
|
+
completeTask(planId: string, taskId: string, findings: Finding[], usage?: TaskUsage | null): void;
|
|
85
|
+
/**
|
|
86
|
+
* Mark a task as errored.
|
|
87
|
+
*
|
|
88
|
+
* Sets the task status to "error" without recording any findings.
|
|
89
|
+
* Updates plan.json and recomputes result aggregations so the error
|
|
90
|
+
* is reflected in completion counts.
|
|
91
|
+
*/
|
|
92
|
+
errorTask(planId: string, taskId: string, errorMessage: string, usage?: TaskUsage | null): void;
|
|
93
|
+
/** Read the results.json for a given plan ID. */
|
|
94
|
+
getResults(planId: string): ReviewResults;
|
|
95
|
+
/** Set the matched and unmatched file lists in the plan. */
|
|
96
|
+
setMatchedFiles(planId: string, matched: string[], unmatched: string[]): void;
|
|
97
|
+
/** Set the per-module summaries in the plan. */
|
|
98
|
+
setModules(planId: string, modules: Record<string, ModuleSummary>): void;
|
|
99
|
+
private planPath;
|
|
100
|
+
private resultsPath;
|
|
101
|
+
private writePlan;
|
|
102
|
+
private writeResults;
|
|
103
|
+
private createEmptyResults;
|
|
104
|
+
/**
|
|
105
|
+
* Load existing results.json or create a new empty structure.
|
|
106
|
+
*/
|
|
107
|
+
private loadOrCreateResults;
|
|
108
|
+
/**
|
|
109
|
+
* Recompute all derived aggregations in results from the task_results
|
|
110
|
+
* and the current plan state.
|
|
111
|
+
*
|
|
112
|
+
* This rebuilds by_file, by_module, summary, and completion from scratch
|
|
113
|
+
* on every completeTask call. Since there is a single orchestrator process,
|
|
114
|
+
* this is safe and keeps the logic simple.
|
|
115
|
+
*/
|
|
116
|
+
private recomputeAggregations;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/core/storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EAGP,aAAa,EACb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EAEV,SAAS,EAEV,MAAM,YAAY,CAAC;AAsCpB;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC,+EAA+E;IAC/E,OAAO,CAAC,MAAM,CAAC,WAAW,CAAqB;gBAanC,UAAU,EAAE,MAAM;IAI9B;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ;IAoDhB,yCAAyC;IACzC,OAAO,CAAC,OAAO;IAQf;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,UAAU;IA0B1D,8CAA8C;IAC9C,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAMnC;;;OAGG;IACH,eAAe,IAAI,MAAM,GAAG,IAAI;IAsBhC,0CAA0C;IAC1C,SAAS,IAAI,KAAK,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IAqCF;;;OAGG;IACH,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU;IAsBxC;;;;;;OAMG;IACH,OAAO,CACL,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,IAAI,CACR,UAAU,EACR,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,SAAS,GACT,cAAc,GACd,QAAQ,GACR,QAAQ,CACX,GACA,UAAU;IAkCb;;;;;OAKG;IACH,SAAS,CACP,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;QACP,WAAW,EAAE,WAAW,CAAC;QACzB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,GACA,UAAU;IA6Bb;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;IAkB7C;;;;;;OAMG;IACH,YAAY,CACV,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,GACvB,IAAI;IAiDP;;;;;;OAMG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI;IA8C/F,iDAAiD;IACjD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa;IAgBzC,4DAA4D;IAC5D,eAAe,CACb,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,EAAE,GAClB,IAAI;IASP,gDAAgD;IAChD,UAAU,CACR,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GACrC,IAAI;IAYP,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,kBAAkB;IAkC1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;CAkH9B"}
|