@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.0",
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.1",
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.0",
976
- "resolved": "https://registry.npmjs.org/@lousy-agents/mcp/-/mcp-5.18.0.tgz",
977
- "integrity": "sha512-rV+JU8TtKZhB4rZJ5jrGFgglz7vb5VA6YZ1qLf9Uv7ybvirUo+ROE9EMkwbwCIyFd3twK5gM5hjrKp72VxINTw==",
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.1",
5855
- "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz",
5856
- "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==",
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.0",
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.1",
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.0",
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.1",
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()(paths, PICOMATCH_OPTIONS) : null;
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 lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
22481
- const hasLockfile = lockfilePath ? await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.lockfile) : false;
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 lockfilePath = pmConfig.lockfile ? await file_system_utils_resolveSafePath(targetDir, pmConfig.lockfile) : undefined;
22499
- const hasLockfile = lockfilePath ? await file_system_utils_pathExistsWithinRoot(targetDir, pmConfig.lockfile) : false;
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
- await this.octokit.rest.repos.createRepoRuleset({
27235
+ const params = {
27231
27236
  owner,
27232
27237
  repo,
27233
- ...payload
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
- */ function createLintCommand(lintLessonsCmd) {
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.1",
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.0",
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",