@lousy-agents/cli 5.18.1 → 5.18.2
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.
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@biomejs/biome": "2.5.3",
|
|
21
|
-
"@lousy-agents/mcp": "5.18.
|
|
21
|
+
"@lousy-agents/mcp": "5.18.1",
|
|
22
22
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
23
23
|
"@testcontainers/postgresql": "12.0.4",
|
|
24
24
|
"@types/node": "24.13.3",
|
|
25
25
|
"@upstash/context7-mcp": "3.2.5",
|
|
26
26
|
"chance": "1.1.13",
|
|
27
27
|
"testcontainers": "12.0.4",
|
|
28
|
-
"tsx": "4.23.
|
|
28
|
+
"tsx": "4.23.4",
|
|
29
29
|
"typescript": "7.0.2",
|
|
30
30
|
"vitest": "4.1.10"
|
|
31
31
|
}
|
|
@@ -972,9 +972,9 @@
|
|
|
972
972
|
}
|
|
973
973
|
},
|
|
974
974
|
"node_modules/@lousy-agents/mcp": {
|
|
975
|
-
"version": "5.18.
|
|
976
|
-
"resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.18.
|
|
977
|
-
"integrity": "sha512-
|
|
975
|
+
"version": "5.18.1",
|
|
976
|
+
"resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.18.1.tgz",
|
|
977
|
+
"integrity": "sha512-G88n6aGiuZ7CE/ziT8kp+BlDbbPY5G/D/iWhZxpwM2f4NJO3AOcIngEt4N+HrODFC3VU65Q8shDJaSGjjsdb5Q==",
|
|
978
978
|
"dev": true,
|
|
979
979
|
"license": "BSD-2-Clause-Patent",
|
|
980
980
|
"dependencies": {
|
|
@@ -5851,9 +5851,9 @@
|
|
|
5851
5851
|
"optional": true
|
|
5852
5852
|
},
|
|
5853
5853
|
"node_modules/tsx": {
|
|
5854
|
-
"version": "4.23.
|
|
5855
|
-
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.
|
|
5856
|
-
"integrity": "sha512-
|
|
5854
|
+
"version": "4.23.4",
|
|
5855
|
+
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.4.tgz",
|
|
5856
|
+
"integrity": "sha512-ZiUQ8oT/KzN51mJUWPqARYqwFLFJZtGZipRkw1ynHMr9vy3eU77m5yfF3Gzm6meEg/beW+lUu3fHYgskTN2oVQ==",
|
|
5857
5857
|
"dev": true,
|
|
5858
5858
|
"license": "MIT",
|
|
5859
5859
|
"dependencies": {
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@biomejs/biome": "2.5.3",
|
|
32
|
-
"@lousy-agents/mcp": "5.18.
|
|
32
|
+
"@lousy-agents/mcp": "5.18.1",
|
|
33
33
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
34
34
|
"@testcontainers/postgresql": "12.0.4",
|
|
35
35
|
"@types/node": "24.13.3",
|
|
36
36
|
"@upstash/context7-mcp": "3.2.5",
|
|
37
37
|
"chance": "1.1.13",
|
|
38
38
|
"testcontainers": "12.0.4",
|
|
39
|
-
"tsx": "4.23.
|
|
39
|
+
"tsx": "4.23.4",
|
|
40
40
|
"typescript": "7.0.2",
|
|
41
41
|
"vitest": "4.1.10"
|
|
42
42
|
}
|
|
@@ -20,12 +20,12 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@biomejs/biome": "2.5.3",
|
|
23
|
-
"@lousy-agents/mcp": "5.18.
|
|
23
|
+
"@lousy-agents/mcp": "5.18.1",
|
|
24
24
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
25
25
|
"@types/node": "24.13.3",
|
|
26
26
|
"@upstash/context7-mcp": "3.2.5",
|
|
27
27
|
"chance": "1.1.13",
|
|
28
|
-
"tsx": "4.23.
|
|
28
|
+
"tsx": "4.23.4",
|
|
29
29
|
"typescript": "7.0.2",
|
|
30
30
|
"vitest": "4.1.10"
|
|
31
31
|
}
|
package/dist/index.js
CHANGED
|
@@ -16811,7 +16811,9 @@ class LessonContextUseCase {
|
|
|
16811
16811
|
// the second, causing incorrect matching).
|
|
16812
16812
|
const globMatchers = result.lessons.map((p)=>{
|
|
16813
16813
|
const { paths } = p.lesson.triggers;
|
|
16814
|
-
return paths.length > 0 ? picomatch_default()(
|
|
16814
|
+
return paths.length > 0 ? picomatch_default()([
|
|
16815
|
+
...paths
|
|
16816
|
+
], PICOMATCH_OPTIONS) : null;
|
|
16815
16817
|
});
|
|
16816
16818
|
matched = result.lessons.filter((p, i)=>matchesLesson(p.lesson, filePaths, fileContents, segments, normalizedPaths, globMatchers[i] ?? null));
|
|
16817
16819
|
}
|
|
@@ -19239,19 +19241,20 @@ function buildPermissionDecisionResponse(payload) {
|
|
|
19239
19241
|
/** Parses a comma-separated --files string into trimmed, non-empty path tokens. */ function parseExplicitFiles(rawFiles) {
|
|
19240
19242
|
return rawFiles.split(",").map((f)=>f.trim()).filter(Boolean);
|
|
19241
19243
|
}
|
|
19244
|
+
const contextArgs = {
|
|
19245
|
+
files: {
|
|
19246
|
+
type: "string",
|
|
19247
|
+
description: "Comma-separated list of file paths to match lessons against (PreToolUse dispatch; for debug use — real hook invocations pass paths via stdin)",
|
|
19248
|
+
default: ""
|
|
19249
|
+
}
|
|
19250
|
+
};
|
|
19242
19251
|
function createContextCommand(useCase) {
|
|
19243
19252
|
return defineCommand({
|
|
19244
19253
|
meta: {
|
|
19245
19254
|
name: "context",
|
|
19246
19255
|
description: "Inject relevant lessons into Claude hooks as additionalContext"
|
|
19247
19256
|
},
|
|
19248
|
-
args:
|
|
19249
|
-
files: {
|
|
19250
|
-
type: "string",
|
|
19251
|
-
description: "Comma-separated list of file paths to match lessons against (PreToolUse dispatch; for debug use — real hook invocations pass paths via stdin)",
|
|
19252
|
-
default: ""
|
|
19253
|
-
}
|
|
19254
|
-
},
|
|
19257
|
+
args: contextArgs,
|
|
19255
19258
|
run: async (context)=>{
|
|
19256
19259
|
const rootDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
19257
19260
|
const rawFiles = typeof context.args?.files === "string" ? context.args.files : "";
|
|
@@ -22477,8 +22480,9 @@ async function readVersionFileContent(targetDir, relativePath) {
|
|
|
22477
22480
|
continue;
|
|
22478
22481
|
}
|
|
22479
22482
|
if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
|
|
22480
|
-
const
|
|
22481
|
-
const
|
|
22483
|
+
const { lockfile } = pmConfig;
|
|
22484
|
+
const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
|
|
22485
|
+
const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
|
|
22482
22486
|
if (pmConfig.requiresLockfile && !hasLockfile) {
|
|
22483
22487
|
continue;
|
|
22484
22488
|
}
|
|
@@ -22495,8 +22499,9 @@ async function readVersionFileContent(targetDir, relativePath) {
|
|
|
22495
22499
|
const packageManagers = [];
|
|
22496
22500
|
for (const pmConfig of otherPackageManagers){
|
|
22497
22501
|
if (await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.manifestFile)) {
|
|
22498
|
-
const
|
|
22499
|
-
const
|
|
22502
|
+
const { lockfile } = pmConfig;
|
|
22503
|
+
const lockfilePath = lockfile ? await file_system_utils_resolveSafePath(targetDir, lockfile) : undefined;
|
|
22504
|
+
const hasLockfile = lockfilePath && lockfile ? await file_system_utils_pathExistsWithinRoot(targetDir, lockfile) : false;
|
|
22500
22505
|
if (pmConfig.requiresLockfile && !hasLockfile) {
|
|
22501
22506
|
continue;
|
|
22502
22507
|
}
|
|
@@ -27227,11 +27232,18 @@ function defaultExec(command, args, options) {
|
|
|
27227
27232
|
throw new Error("Not authenticated");
|
|
27228
27233
|
}
|
|
27229
27234
|
try {
|
|
27230
|
-
|
|
27235
|
+
const params = {
|
|
27231
27236
|
owner,
|
|
27232
27237
|
repo,
|
|
27233
|
-
|
|
27234
|
-
|
|
27238
|
+
name: payload.name,
|
|
27239
|
+
enforcement: payload.enforcement,
|
|
27240
|
+
target: payload.target,
|
|
27241
|
+
// biome-ignore lint/style/useNamingConvention: GitHub API schema requires snake_case
|
|
27242
|
+
bypass_actors: payload.bypass_actors,
|
|
27243
|
+
conditions: payload.conditions,
|
|
27244
|
+
rules: payload.rules
|
|
27245
|
+
};
|
|
27246
|
+
await this.octokit.rest.repos.createRepoRuleset(params);
|
|
27235
27247
|
} catch (error) {
|
|
27236
27248
|
const details = formatOctokitError(error);
|
|
27237
27249
|
throw new Error(`Failed to create ruleset for ${owner}/${repo}: ${details}`);
|
|
@@ -32672,24 +32684,25 @@ const initCommand = defineCommand({
|
|
|
32672
32684
|
;// CONCATENATED MODULE: ./src/commands/init-hooks.ts
|
|
32673
32685
|
|
|
32674
32686
|
|
|
32687
|
+
const initHooksArgs = {
|
|
32688
|
+
force: {
|
|
32689
|
+
type: "boolean",
|
|
32690
|
+
description: "Overwrite existing hook configuration even if already present",
|
|
32691
|
+
default: false
|
|
32692
|
+
},
|
|
32693
|
+
"no-session-start": {
|
|
32694
|
+
type: "boolean",
|
|
32695
|
+
description: "Disable the SessionStart hook for invariant lesson injection (enabled by default)",
|
|
32696
|
+
default: false
|
|
32697
|
+
}
|
|
32698
|
+
};
|
|
32675
32699
|
function createInitHooksCommand(useCase) {
|
|
32676
32700
|
return defineCommand({
|
|
32677
32701
|
meta: {
|
|
32678
32702
|
name: "init-hooks",
|
|
32679
32703
|
description: "Initialize Claude Code hook configuration for lousy-agents context injection"
|
|
32680
32704
|
},
|
|
32681
|
-
args:
|
|
32682
|
-
force: {
|
|
32683
|
-
type: "boolean",
|
|
32684
|
-
description: "Overwrite existing hook configuration even if already present",
|
|
32685
|
-
default: false
|
|
32686
|
-
},
|
|
32687
|
-
"no-session-start": {
|
|
32688
|
-
type: "boolean",
|
|
32689
|
-
description: "Disable the SessionStart hook for invariant lesson injection (enabled by default)",
|
|
32690
|
-
default: false
|
|
32691
|
-
}
|
|
32692
|
-
},
|
|
32705
|
+
args: initHooksArgs,
|
|
32693
32706
|
run: async (context)=>{
|
|
32694
32707
|
const rootDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
32695
32708
|
const force = context.args?.force === true;
|
|
@@ -51905,227 +51918,6 @@ function lib_isUint8Array(value) {
|
|
|
51905
51918
|
*/
|
|
51906
51919
|
const remark = unified().use(remarkParse).use(remarkStringify).freeze()
|
|
51907
51920
|
|
|
51908
|
-
;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
|
|
51909
|
-
/**
|
|
51910
|
-
* Gateway for parsing Markdown files into AST using remark.
|
|
51911
|
-
* Extracts structural features like headings, code blocks, and paragraphs.
|
|
51912
|
-
*/
|
|
51913
|
-
|
|
51914
|
-
|
|
51915
|
-
/** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
|
|
51916
|
-
/**
|
|
51917
|
-
* Remark-based implementation of the Markdown AST gateway.
|
|
51918
|
-
*/ class RemarkMarkdownAstGateway {
|
|
51919
|
-
async parseFile(filePath) {
|
|
51920
|
-
const content = await readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
|
|
51921
|
-
return this.parseContent(content);
|
|
51922
|
-
}
|
|
51923
|
-
parseContent(content) {
|
|
51924
|
-
const processor = remark();
|
|
51925
|
-
const ast = processor.parse(content);
|
|
51926
|
-
const headings = [];
|
|
51927
|
-
const codeBlocks = [];
|
|
51928
|
-
const inlineCodes = [];
|
|
51929
|
-
lib_visit(ast, "heading", (node)=>{
|
|
51930
|
-
const text = this.extractHeadingText(node);
|
|
51931
|
-
headings.push({
|
|
51932
|
-
text,
|
|
51933
|
-
depth: node.depth,
|
|
51934
|
-
position: {
|
|
51935
|
-
line: node.position?.start.line ?? 0
|
|
51936
|
-
}
|
|
51937
|
-
});
|
|
51938
|
-
});
|
|
51939
|
-
// Track code blocks with their node index in root children
|
|
51940
|
-
for(let i = 0; i < ast.children.length; i++){
|
|
51941
|
-
const child = ast.children[i];
|
|
51942
|
-
if (child.type === "code") {
|
|
51943
|
-
const codeNode = child;
|
|
51944
|
-
codeBlocks.push({
|
|
51945
|
-
value: codeNode.value,
|
|
51946
|
-
lang: codeNode.lang ?? undefined,
|
|
51947
|
-
position: {
|
|
51948
|
-
line: codeNode.position?.start.line ?? 0
|
|
51949
|
-
},
|
|
51950
|
-
nodeIndex: i
|
|
51951
|
-
});
|
|
51952
|
-
}
|
|
51953
|
-
}
|
|
51954
|
-
lib_visit(ast, "inlineCode", (node)=>{
|
|
51955
|
-
inlineCodes.push({
|
|
51956
|
-
value: node.value,
|
|
51957
|
-
position: {
|
|
51958
|
-
line: node.position?.start.line ?? 0
|
|
51959
|
-
}
|
|
51960
|
-
});
|
|
51961
|
-
});
|
|
51962
|
-
return {
|
|
51963
|
-
headings,
|
|
51964
|
-
codeBlocks,
|
|
51965
|
-
inlineCodes,
|
|
51966
|
-
ast
|
|
51967
|
-
};
|
|
51968
|
-
}
|
|
51969
|
-
findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
|
|
51970
|
-
const ast = structure.ast;
|
|
51971
|
-
const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
|
|
51972
|
-
for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
|
|
51973
|
-
const sibling = ast.children[i];
|
|
51974
|
-
const text = extractTextFromNode(sibling);
|
|
51975
|
-
const lowerText = text.toLowerCase();
|
|
51976
|
-
for (const keyword of keywords){
|
|
51977
|
-
if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
|
|
51978
|
-
return true;
|
|
51979
|
-
}
|
|
51980
|
-
}
|
|
51981
|
-
}
|
|
51982
|
-
return false;
|
|
51983
|
-
}
|
|
51984
|
-
extractHeadingText(node) {
|
|
51985
|
-
const parts = [];
|
|
51986
|
-
lib_visit(node, "text", (textNode)=>{
|
|
51987
|
-
parts.push(textNode.value);
|
|
51988
|
-
});
|
|
51989
|
-
return parts.join("");
|
|
51990
|
-
}
|
|
51991
|
-
}
|
|
51992
|
-
/**
|
|
51993
|
-
* Checks if text contains a keyword at a word boundary (safe string matching, no regex).
|
|
51994
|
-
*/ function containsWordBoundary(text, keyword) {
|
|
51995
|
-
const index = text.indexOf(keyword);
|
|
51996
|
-
if (index === -1) {
|
|
51997
|
-
return false;
|
|
51998
|
-
}
|
|
51999
|
-
const charBefore = index > 0 ? text[index - 1] : " ";
|
|
52000
|
-
const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
|
|
52001
|
-
const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
|
|
52002
|
-
const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
|
|
52003
|
-
return isWordBoundaryBefore && isWordBoundaryAfter;
|
|
52004
|
-
}
|
|
52005
|
-
/**
|
|
52006
|
-
* Extracts plain text content from any AST node recursively.
|
|
52007
|
-
*/ function extractTextFromNode(node) {
|
|
52008
|
-
if (node.value && typeof node.value === "string") {
|
|
52009
|
-
return node.value;
|
|
52010
|
-
}
|
|
52011
|
-
if (Array.isArray(node.children)) {
|
|
52012
|
-
return node.children.map((child)=>extractTextFromNode(child)).join(" ");
|
|
52013
|
-
}
|
|
52014
|
-
return "";
|
|
52015
|
-
}
|
|
52016
|
-
/**
|
|
52017
|
-
* Creates and returns the default Markdown AST gateway.
|
|
52018
|
-
*/ function createMarkdownAstGateway() {
|
|
52019
|
-
return new RemarkMarkdownAstGateway();
|
|
52020
|
-
}
|
|
52021
|
-
|
|
52022
|
-
;// CONCATENATED MODULE: ../core/src/entities/lint-rules.ts
|
|
52023
|
-
/**
|
|
52024
|
-
* Lint rule registry entity.
|
|
52025
|
-
* Defines all known lint rule IDs with their default severities, organized by target.
|
|
52026
|
-
*/ /** Valid severity values for rule configuration */ /** Default severity levels for all known lint rules */ const DEFAULT_LINT_RULES = {
|
|
52027
|
-
agents: {
|
|
52028
|
-
"agent/missing-frontmatter": "error",
|
|
52029
|
-
"agent/invalid-frontmatter": "error",
|
|
52030
|
-
"agent/missing-name": "error",
|
|
52031
|
-
"agent/invalid-name-format": "error",
|
|
52032
|
-
"agent/name-mismatch": "error",
|
|
52033
|
-
"agent/missing-description": "error",
|
|
52034
|
-
"agent/invalid-description": "error",
|
|
52035
|
-
"agent/invalid-field": "warn"
|
|
52036
|
-
},
|
|
52037
|
-
hooks: {
|
|
52038
|
-
"hook/invalid-json": "error",
|
|
52039
|
-
"hook/invalid-config": "error",
|
|
52040
|
-
"hook/missing-command": "error",
|
|
52041
|
-
"hook/missing-matcher": "warn",
|
|
52042
|
-
"hook/missing-timeout": "warn"
|
|
52043
|
-
},
|
|
52044
|
-
instructions: {
|
|
52045
|
-
"instruction/parse-error": "warn",
|
|
52046
|
-
"instruction/command-not-in-code-block": "warn",
|
|
52047
|
-
"instruction/command-outside-section": "warn",
|
|
52048
|
-
"instruction/missing-error-handling": "warn",
|
|
52049
|
-
"instruction/missing-structural-heading": "warn"
|
|
52050
|
-
},
|
|
52051
|
-
skills: {
|
|
52052
|
-
"skill/invalid-frontmatter": "error",
|
|
52053
|
-
"skill/missing-frontmatter": "error",
|
|
52054
|
-
"skill/missing-name": "error",
|
|
52055
|
-
"skill/invalid-name-format": "error",
|
|
52056
|
-
"skill/name-mismatch": "error",
|
|
52057
|
-
"skill/missing-description": "error",
|
|
52058
|
-
"skill/invalid-description": "error",
|
|
52059
|
-
"skill/missing-allowed-tools": "warn",
|
|
52060
|
-
"skill/missing-argument-hint": "warn"
|
|
52061
|
-
}
|
|
52062
|
-
};
|
|
52063
|
-
|
|
52064
|
-
;// CONCATENATED MODULE: ../core/src/lib/lint-config.ts
|
|
52065
|
-
/**
|
|
52066
|
-
* Lint configuration loader.
|
|
52067
|
-
* Loads lint rule severity overrides from c12 config and merges with defaults.
|
|
52068
|
-
*/
|
|
52069
|
-
|
|
52070
|
-
|
|
52071
|
-
/** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = schemas_record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
|
|
52072
|
-
"error",
|
|
52073
|
-
"warn",
|
|
52074
|
-
"off"
|
|
52075
|
-
]));
|
|
52076
|
-
/** Zod schema for the lint.rules section of the config */ const LintRulesConfigSchema = schemas_object({
|
|
52077
|
-
agents: RuleConfigMapSchema.optional(),
|
|
52078
|
-
hooks: RuleConfigMapSchema.optional(),
|
|
52079
|
-
instructions: RuleConfigMapSchema.optional(),
|
|
52080
|
-
skills: RuleConfigMapSchema.optional()
|
|
52081
|
-
});
|
|
52082
|
-
/** Zod schema for the lint section of the config */ const LintConfigSchema = schemas_object({
|
|
52083
|
-
lint: schemas_object({
|
|
52084
|
-
rules: LintRulesConfigSchema.optional()
|
|
52085
|
-
}).optional()
|
|
52086
|
-
});
|
|
52087
|
-
/**
|
|
52088
|
-
* Merges user overrides with defaults for a single target.
|
|
52089
|
-
* Only known rule IDs (present in defaults) are applied; unknown IDs are discarded.
|
|
52090
|
-
*/ function mergeTargetRules(defaults, overrides) {
|
|
52091
|
-
if (!overrides) {
|
|
52092
|
-
return defaults;
|
|
52093
|
-
}
|
|
52094
|
-
const merged = {
|
|
52095
|
-
...defaults
|
|
52096
|
-
};
|
|
52097
|
-
for (const [ruleId, severity] of Object.entries(overrides)){
|
|
52098
|
-
if (Object.hasOwn(defaults, ruleId)) {
|
|
52099
|
-
merged[ruleId] = severity;
|
|
52100
|
-
}
|
|
52101
|
-
}
|
|
52102
|
-
return merged;
|
|
52103
|
-
}
|
|
52104
|
-
/**
|
|
52105
|
-
* Loads lint configuration from the target directory using c12.
|
|
52106
|
-
* Merges user overrides with default rule severities.
|
|
52107
|
-
* Throws on config load failures (syntax errors, permission denied, validation errors).
|
|
52108
|
-
*/ async function loadLintConfig(targetDir) {
|
|
52109
|
-
const { config } = await loadConfig({
|
|
52110
|
-
name: "lousy-agents",
|
|
52111
|
-
cwd: targetDir
|
|
52112
|
-
});
|
|
52113
|
-
if (!config) {
|
|
52114
|
-
return DEFAULT_LINT_RULES;
|
|
52115
|
-
}
|
|
52116
|
-
const parsed = LintConfigSchema.parse(config);
|
|
52117
|
-
const rules = parsed.lint?.rules;
|
|
52118
|
-
if (!rules) {
|
|
52119
|
-
return DEFAULT_LINT_RULES;
|
|
52120
|
-
}
|
|
52121
|
-
return {
|
|
52122
|
-
agents: mergeTargetRules(DEFAULT_LINT_RULES.agents, rules.agents),
|
|
52123
|
-
hooks: mergeTargetRules(DEFAULT_LINT_RULES.hooks, rules.hooks),
|
|
52124
|
-
instructions: mergeTargetRules(DEFAULT_LINT_RULES.instructions, rules.instructions),
|
|
52125
|
-
skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills)
|
|
52126
|
-
};
|
|
52127
|
-
}
|
|
52128
|
-
|
|
52129
51921
|
;// CONCATENATED MODULE: ../core/src/entities/instruction-quality.ts
|
|
52130
51922
|
/**
|
|
52131
51923
|
* Core domain entities for instruction quality analysis.
|
|
@@ -52177,6 +51969,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52177
51969
|
* Orchestrates discovery, AST parsing, and scoring of feedback loop documentation.
|
|
52178
51970
|
*/
|
|
52179
51971
|
|
|
51972
|
+
/** Type guard narrowing an `unknown` AST child to {@link AstNodeLike}. */ function isAstNodeLike(node) {
|
|
51973
|
+
return typeof node === "object" && node !== null && "type" in node && typeof node.type === "string";
|
|
51974
|
+
}
|
|
52180
51975
|
/** Maximum number of raw heading pattern entries accepted before deduplication. */ const MAX_RAW_HEADING_PATTERNS = 1000;
|
|
52181
51976
|
/** Maximum number of heading patterns accepted in a single execute() call. */ const MAX_HEADING_PATTERNS = 50;
|
|
52182
51977
|
/** Maximum character length for a single heading pattern. */ const analyze_instruction_quality_MAX_PATTERN_LENGTH = 200;
|
|
@@ -52532,6 +52327,9 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52532
52327
|
return parts.join(" ");
|
|
52533
52328
|
}
|
|
52534
52329
|
collectText(node, parts) {
|
|
52330
|
+
if (!isAstNodeLike(node)) {
|
|
52331
|
+
return;
|
|
52332
|
+
}
|
|
52535
52333
|
if (node.type === "text" && typeof node.value === "string") {
|
|
52536
52334
|
parts.push(node.value);
|
|
52537
52335
|
}
|
|
@@ -52678,6 +52476,231 @@ const HEADING_PATTERN_DESCRIPTIONS = new Map(Object.entries(HEADING_PATTERN_DESC
|
|
|
52678
52476
|
}
|
|
52679
52477
|
}
|
|
52680
52478
|
|
|
52479
|
+
;// CONCATENATED MODULE: ../core/src/gateways/markdown-ast-gateway.ts
|
|
52480
|
+
/**
|
|
52481
|
+
* Gateway for parsing Markdown files into AST using remark.
|
|
52482
|
+
* Extracts structural features like headings, code blocks, and paragraphs.
|
|
52483
|
+
*/
|
|
52484
|
+
|
|
52485
|
+
|
|
52486
|
+
|
|
52487
|
+
/** Maximum instruction file size: 1 MB */ const markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES = 1_048_576;
|
|
52488
|
+
/**
|
|
52489
|
+
* Remark-based implementation of the Markdown AST gateway.
|
|
52490
|
+
*/ class RemarkMarkdownAstGateway {
|
|
52491
|
+
async parseFile(filePath) {
|
|
52492
|
+
const content = await readFileNoFollow(filePath, markdown_ast_gateway_MAX_INSTRUCTION_FILE_BYTES);
|
|
52493
|
+
return this.parseContent(content);
|
|
52494
|
+
}
|
|
52495
|
+
parseContent(content) {
|
|
52496
|
+
const processor = remark();
|
|
52497
|
+
const ast = processor.parse(content);
|
|
52498
|
+
const headings = [];
|
|
52499
|
+
const codeBlocks = [];
|
|
52500
|
+
const inlineCodes = [];
|
|
52501
|
+
lib_visit(ast, "heading", (node)=>{
|
|
52502
|
+
const text = this.extractHeadingText(node);
|
|
52503
|
+
headings.push({
|
|
52504
|
+
text,
|
|
52505
|
+
depth: node.depth,
|
|
52506
|
+
position: {
|
|
52507
|
+
line: node.position?.start.line ?? 0
|
|
52508
|
+
}
|
|
52509
|
+
});
|
|
52510
|
+
});
|
|
52511
|
+
// Track code blocks with their node index in root children
|
|
52512
|
+
for(let i = 0; i < ast.children.length; i++){
|
|
52513
|
+
const child = ast.children[i];
|
|
52514
|
+
if (child.type === "code") {
|
|
52515
|
+
const codeNode = child;
|
|
52516
|
+
codeBlocks.push({
|
|
52517
|
+
value: codeNode.value,
|
|
52518
|
+
lang: codeNode.lang ?? undefined,
|
|
52519
|
+
position: {
|
|
52520
|
+
line: codeNode.position?.start.line ?? 0
|
|
52521
|
+
},
|
|
52522
|
+
nodeIndex: i
|
|
52523
|
+
});
|
|
52524
|
+
}
|
|
52525
|
+
}
|
|
52526
|
+
lib_visit(ast, "inlineCode", (node)=>{
|
|
52527
|
+
inlineCodes.push({
|
|
52528
|
+
value: node.value,
|
|
52529
|
+
position: {
|
|
52530
|
+
line: node.position?.start.line ?? 0
|
|
52531
|
+
}
|
|
52532
|
+
});
|
|
52533
|
+
});
|
|
52534
|
+
return {
|
|
52535
|
+
headings,
|
|
52536
|
+
codeBlocks,
|
|
52537
|
+
inlineCodes,
|
|
52538
|
+
ast
|
|
52539
|
+
};
|
|
52540
|
+
}
|
|
52541
|
+
findConditionalKeywordsInProximity(structure, codeBlockNodeIndex, proximityWindow, keywords) {
|
|
52542
|
+
const ast = structure.ast;
|
|
52543
|
+
const endIndex = Math.min(ast.children.length, codeBlockNodeIndex + 1 + proximityWindow);
|
|
52544
|
+
for(let i = codeBlockNodeIndex + 1; i < endIndex; i++){
|
|
52545
|
+
const sibling = ast.children[i];
|
|
52546
|
+
const text = extractTextFromNode(sibling);
|
|
52547
|
+
const lowerText = text.toLowerCase();
|
|
52548
|
+
for (const keyword of keywords){
|
|
52549
|
+
if (containsWordBoundary(lowerText, keyword.toLowerCase())) {
|
|
52550
|
+
return true;
|
|
52551
|
+
}
|
|
52552
|
+
}
|
|
52553
|
+
}
|
|
52554
|
+
return false;
|
|
52555
|
+
}
|
|
52556
|
+
extractHeadingText(node) {
|
|
52557
|
+
const parts = [];
|
|
52558
|
+
lib_visit(node, "text", (textNode)=>{
|
|
52559
|
+
parts.push(textNode.value);
|
|
52560
|
+
});
|
|
52561
|
+
return parts.join("");
|
|
52562
|
+
}
|
|
52563
|
+
}
|
|
52564
|
+
/**
|
|
52565
|
+
* Checks if text contains a keyword at a word boundary (safe string matching, no regex).
|
|
52566
|
+
*/ function containsWordBoundary(text, keyword) {
|
|
52567
|
+
const index = text.indexOf(keyword);
|
|
52568
|
+
if (index === -1) {
|
|
52569
|
+
return false;
|
|
52570
|
+
}
|
|
52571
|
+
const charBefore = index > 0 ? text[index - 1] : " ";
|
|
52572
|
+
const charAfter = index + keyword.length < text.length ? text[index + keyword.length] : " ";
|
|
52573
|
+
const isWordBoundaryBefore = !/[a-z0-9]/i.test(charBefore);
|
|
52574
|
+
const isWordBoundaryAfter = !/[a-z0-9]/i.test(charAfter);
|
|
52575
|
+
return isWordBoundaryBefore && isWordBoundaryAfter;
|
|
52576
|
+
}
|
|
52577
|
+
/**
|
|
52578
|
+
* Extracts plain text content from any AST node recursively.
|
|
52579
|
+
*/ function extractTextFromNode(node) {
|
|
52580
|
+
if (!isAstNodeLike(node)) {
|
|
52581
|
+
return "";
|
|
52582
|
+
}
|
|
52583
|
+
if (node.value && typeof node.value === "string") {
|
|
52584
|
+
return node.value;
|
|
52585
|
+
}
|
|
52586
|
+
if (Array.isArray(node.children)) {
|
|
52587
|
+
return node.children.map((child)=>extractTextFromNode(child)).join(" ");
|
|
52588
|
+
}
|
|
52589
|
+
return "";
|
|
52590
|
+
}
|
|
52591
|
+
/**
|
|
52592
|
+
* Creates and returns the default Markdown AST gateway.
|
|
52593
|
+
*/ function createMarkdownAstGateway() {
|
|
52594
|
+
return new RemarkMarkdownAstGateway();
|
|
52595
|
+
}
|
|
52596
|
+
|
|
52597
|
+
;// CONCATENATED MODULE: ../core/src/entities/lint-rules.ts
|
|
52598
|
+
/**
|
|
52599
|
+
* Lint rule registry entity.
|
|
52600
|
+
* Defines all known lint rule IDs with their default severities, organized by target.
|
|
52601
|
+
*/ /** Valid severity values for rule configuration */ /** Default severity levels for all known lint rules */ const DEFAULT_LINT_RULES = {
|
|
52602
|
+
agents: {
|
|
52603
|
+
"agent/missing-frontmatter": "error",
|
|
52604
|
+
"agent/invalid-frontmatter": "error",
|
|
52605
|
+
"agent/missing-name": "error",
|
|
52606
|
+
"agent/invalid-name-format": "error",
|
|
52607
|
+
"agent/name-mismatch": "error",
|
|
52608
|
+
"agent/missing-description": "error",
|
|
52609
|
+
"agent/invalid-description": "error",
|
|
52610
|
+
"agent/invalid-field": "warn"
|
|
52611
|
+
},
|
|
52612
|
+
hooks: {
|
|
52613
|
+
"hook/invalid-json": "error",
|
|
52614
|
+
"hook/invalid-config": "error",
|
|
52615
|
+
"hook/missing-command": "error",
|
|
52616
|
+
"hook/missing-matcher": "warn",
|
|
52617
|
+
"hook/missing-timeout": "warn"
|
|
52618
|
+
},
|
|
52619
|
+
instructions: {
|
|
52620
|
+
"instruction/parse-error": "warn",
|
|
52621
|
+
"instruction/command-not-in-code-block": "warn",
|
|
52622
|
+
"instruction/command-outside-section": "warn",
|
|
52623
|
+
"instruction/missing-error-handling": "warn",
|
|
52624
|
+
"instruction/missing-structural-heading": "warn"
|
|
52625
|
+
},
|
|
52626
|
+
skills: {
|
|
52627
|
+
"skill/invalid-frontmatter": "error",
|
|
52628
|
+
"skill/missing-frontmatter": "error",
|
|
52629
|
+
"skill/missing-name": "error",
|
|
52630
|
+
"skill/invalid-name-format": "error",
|
|
52631
|
+
"skill/name-mismatch": "error",
|
|
52632
|
+
"skill/missing-description": "error",
|
|
52633
|
+
"skill/invalid-description": "error",
|
|
52634
|
+
"skill/missing-allowed-tools": "warn",
|
|
52635
|
+
"skill/missing-argument-hint": "warn"
|
|
52636
|
+
}
|
|
52637
|
+
};
|
|
52638
|
+
|
|
52639
|
+
;// CONCATENATED MODULE: ../core/src/lib/lint-config.ts
|
|
52640
|
+
/**
|
|
52641
|
+
* Lint configuration loader.
|
|
52642
|
+
* Loads lint rule severity overrides from c12 config and merges with defaults.
|
|
52643
|
+
*/
|
|
52644
|
+
|
|
52645
|
+
|
|
52646
|
+
/** Zod schema for a rule config map: rule IDs validated with regex to prevent prototype pollution */ const RuleConfigMapSchema = schemas_record(schemas_string().regex(/^[a-z]+\/[a-z]+(?:-[a-z]+)*$/), schemas_enum([
|
|
52647
|
+
"error",
|
|
52648
|
+
"warn",
|
|
52649
|
+
"off"
|
|
52650
|
+
]));
|
|
52651
|
+
/** Zod schema for the lint.rules section of the config */ const LintRulesConfigSchema = schemas_object({
|
|
52652
|
+
agents: RuleConfigMapSchema.optional(),
|
|
52653
|
+
hooks: RuleConfigMapSchema.optional(),
|
|
52654
|
+
instructions: RuleConfigMapSchema.optional(),
|
|
52655
|
+
skills: RuleConfigMapSchema.optional()
|
|
52656
|
+
});
|
|
52657
|
+
/** Zod schema for the lint section of the config */ const LintConfigSchema = schemas_object({
|
|
52658
|
+
lint: schemas_object({
|
|
52659
|
+
rules: LintRulesConfigSchema.optional()
|
|
52660
|
+
}).optional()
|
|
52661
|
+
});
|
|
52662
|
+
/**
|
|
52663
|
+
* Merges user overrides with defaults for a single target.
|
|
52664
|
+
* Only known rule IDs (present in defaults) are applied; unknown IDs are discarded.
|
|
52665
|
+
*/ function mergeTargetRules(defaults, overrides) {
|
|
52666
|
+
if (!overrides) {
|
|
52667
|
+
return defaults;
|
|
52668
|
+
}
|
|
52669
|
+
const merged = {
|
|
52670
|
+
...defaults
|
|
52671
|
+
};
|
|
52672
|
+
for (const [ruleId, severity] of Object.entries(overrides)){
|
|
52673
|
+
if (Object.hasOwn(defaults, ruleId)) {
|
|
52674
|
+
merged[ruleId] = severity;
|
|
52675
|
+
}
|
|
52676
|
+
}
|
|
52677
|
+
return merged;
|
|
52678
|
+
}
|
|
52679
|
+
/**
|
|
52680
|
+
* Loads lint configuration from the target directory using c12.
|
|
52681
|
+
* Merges user overrides with default rule severities.
|
|
52682
|
+
* Throws on config load failures (syntax errors, permission denied, validation errors).
|
|
52683
|
+
*/ async function loadLintConfig(targetDir) {
|
|
52684
|
+
const { config } = await loadConfig({
|
|
52685
|
+
name: "lousy-agents",
|
|
52686
|
+
cwd: targetDir
|
|
52687
|
+
});
|
|
52688
|
+
if (!config) {
|
|
52689
|
+
return DEFAULT_LINT_RULES;
|
|
52690
|
+
}
|
|
52691
|
+
const parsed = LintConfigSchema.parse(config);
|
|
52692
|
+
const rules = parsed.lint?.rules;
|
|
52693
|
+
if (!rules) {
|
|
52694
|
+
return DEFAULT_LINT_RULES;
|
|
52695
|
+
}
|
|
52696
|
+
return {
|
|
52697
|
+
agents: mergeTargetRules(DEFAULT_LINT_RULES.agents, rules.agents),
|
|
52698
|
+
hooks: mergeTargetRules(DEFAULT_LINT_RULES.hooks, rules.hooks),
|
|
52699
|
+
instructions: mergeTargetRules(DEFAULT_LINT_RULES.instructions, rules.instructions),
|
|
52700
|
+
skills: mergeTargetRules(DEFAULT_LINT_RULES.skills, rules.skills)
|
|
52701
|
+
};
|
|
52702
|
+
}
|
|
52703
|
+
|
|
52681
52704
|
;// CONCATENATED MODULE: ../core/src/use-cases/apply-severity-filter.ts
|
|
52682
52705
|
/**
|
|
52683
52706
|
* Shared severity filtering for lint outputs.
|
|
@@ -53627,7 +53650,34 @@ function isTargetEnabled(key, targets) {
|
|
|
53627
53650
|
/**
|
|
53628
53651
|
* The `lint` command for validating agent skills, custom agents, and instruction files.
|
|
53629
53652
|
* Also exposes `lint lessons` as a subcommand for lesson frontmatter validation.
|
|
53630
|
-
*/
|
|
53653
|
+
*/ const lintArgs = {
|
|
53654
|
+
skills: {
|
|
53655
|
+
type: "boolean",
|
|
53656
|
+
description: "Lint skill frontmatter in .github/skills/ and .claude/skills/",
|
|
53657
|
+
default: false
|
|
53658
|
+
},
|
|
53659
|
+
agents: {
|
|
53660
|
+
type: "boolean",
|
|
53661
|
+
description: "Lint custom agent frontmatter in .github/agents/",
|
|
53662
|
+
default: false
|
|
53663
|
+
},
|
|
53664
|
+
hooks: {
|
|
53665
|
+
type: "boolean",
|
|
53666
|
+
description: "Lint pre-tool-use hook configurations in .github/hooks/agent-shell/hooks.json, .claude/settings.json, and .claude/settings.local.json",
|
|
53667
|
+
default: false
|
|
53668
|
+
},
|
|
53669
|
+
instructions: {
|
|
53670
|
+
type: "boolean",
|
|
53671
|
+
description: "Analyze instruction quality across all instruction file formats",
|
|
53672
|
+
default: false
|
|
53673
|
+
},
|
|
53674
|
+
format: {
|
|
53675
|
+
type: "string",
|
|
53676
|
+
description: "Output format: human (default), json, or rdjsonl",
|
|
53677
|
+
default: "human"
|
|
53678
|
+
}
|
|
53679
|
+
};
|
|
53680
|
+
function createLintCommand(lintLessonsCmd) {
|
|
53631
53681
|
return defineCommand({
|
|
53632
53682
|
meta: {
|
|
53633
53683
|
name: "lint",
|
|
@@ -53636,33 +53686,7 @@ function isTargetEnabled(key, targets) {
|
|
|
53636
53686
|
subCommands: {
|
|
53637
53687
|
lessons: lintLessonsCmd
|
|
53638
53688
|
},
|
|
53639
|
-
args:
|
|
53640
|
-
skills: {
|
|
53641
|
-
type: "boolean",
|
|
53642
|
-
description: "Lint skill frontmatter in .github/skills/ and .claude/skills/",
|
|
53643
|
-
default: false
|
|
53644
|
-
},
|
|
53645
|
-
agents: {
|
|
53646
|
-
type: "boolean",
|
|
53647
|
-
description: "Lint custom agent frontmatter in .github/agents/",
|
|
53648
|
-
default: false
|
|
53649
|
-
},
|
|
53650
|
-
hooks: {
|
|
53651
|
-
type: "boolean",
|
|
53652
|
-
description: "Lint pre-tool-use hook configurations in .github/hooks/agent-shell/hooks.json, .claude/settings.json, and .claude/settings.local.json",
|
|
53653
|
-
default: false
|
|
53654
|
-
},
|
|
53655
|
-
instructions: {
|
|
53656
|
-
type: "boolean",
|
|
53657
|
-
description: "Analyze instruction quality across all instruction file formats",
|
|
53658
|
-
default: false
|
|
53659
|
-
},
|
|
53660
|
-
format: {
|
|
53661
|
-
type: "string",
|
|
53662
|
-
description: "Output format: human (default), json, or rdjsonl",
|
|
53663
|
-
default: "human"
|
|
53664
|
-
}
|
|
53665
|
-
},
|
|
53689
|
+
args: lintArgs,
|
|
53666
53690
|
run: async (context)=>{
|
|
53667
53691
|
const rawTargetDir = typeof context.data?.targetDir === "string" ? context.data.targetDir : process.cwd();
|
|
53668
53692
|
const rawArgs_ = context.rawArgs ?? [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lousy-agents/cli",
|
|
3
|
-
"version": "5.18.
|
|
3
|
+
"version": "5.18.2",
|
|
4
4
|
"description": "CLI scaffolding tool that sets up projects with structure, instructions, and feedback loops for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"lousy-agents": "dist/index.js"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"build": "rspack build --config rspack.config.ts && chmod +x dist/index.js"
|
|
41
|
+
"build": "rspack build --config rspack.config.ts && chmod +x dist/index.js",
|
|
42
|
+
"typecheck": "tsc --noEmit"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"c12": "3.3.4",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@biomejs/biome": "2.5.3",
|
|
25
25
|
"@eslint/eslintrc": "3.3.6",
|
|
26
|
-
"@lousy-agents/mcp": "5.18.
|
|
26
|
+
"@lousy-agents/mcp": "5.18.1",
|
|
27
27
|
"@modelcontextprotocol/server-sequential-thinking": "2026.7.4",
|
|
28
28
|
"@testing-library/jest-dom": "7.0.0",
|
|
29
29
|
"@testing-library/react": "16.3.2",
|