@lousy-agents/cli 4.0.1 → 5.0.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.
@@ -0,0 +1,45 @@
1
+ import type { AddressInfo } from "node:net";
2
+ import type { FastifyInstance } from "fastify";
3
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
4
+ import { buildApp } from "./app.js";
5
+
6
+ describe("API integration", () => {
7
+ let app: FastifyInstance;
8
+ let baseUrl: string;
9
+
10
+ beforeAll(async () => {
11
+ app = buildApp();
12
+ await app.listen({ port: 0, host: "127.0.0.1" });
13
+ const address = app.server.address() as AddressInfo;
14
+ baseUrl = `http://127.0.0.1:${address.port}`;
15
+ });
16
+
17
+ afterAll(async () => {
18
+ await app.close();
19
+ });
20
+
21
+ describe("GET /health", () => {
22
+ it("should respond with 200 and health status", async () => {
23
+ const response = await fetch(`${baseUrl}/health`);
24
+
25
+ expect(response.status).toBe(200);
26
+ expect(await response.json()).toEqual({ status: "ok" });
27
+ });
28
+
29
+ it("should return application/json content type", async () => {
30
+ const response = await fetch(`${baseUrl}/health`);
31
+
32
+ expect(response.headers.get("content-type")).toContain(
33
+ "application/json",
34
+ );
35
+ });
36
+ });
37
+
38
+ describe("undefined routes", () => {
39
+ it("should return 404 for non-existent routes", async () => {
40
+ const response = await fetch(`${baseUrl}/nonexistent`);
41
+
42
+ expect(response.status).toBe(404);
43
+ });
44
+ });
45
+ });
@@ -0,0 +1,28 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+ import { buildApp } from "./app.js";
4
+
5
+ describe("Health endpoint", () => {
6
+ let app: FastifyInstance;
7
+
8
+ beforeAll(async () => {
9
+ app = buildApp();
10
+ await app.ready();
11
+ });
12
+
13
+ afterAll(async () => {
14
+ await app.close();
15
+ });
16
+
17
+ describe("GET /health", () => {
18
+ it("should return 200 with status ok", async () => {
19
+ const response = await app.inject({
20
+ method: "GET",
21
+ url: "/health",
22
+ });
23
+
24
+ expect(response.statusCode).toBe(200);
25
+ expect(response.json()).toEqual({ status: "ok" });
26
+ });
27
+ });
28
+ });
@@ -0,0 +1,11 @@
1
+ import Fastify from "fastify";
2
+
3
+ export function buildApp() {
4
+ const app = Fastify({ logger: true });
5
+
6
+ app.get("/health", async () => {
7
+ return { status: "ok" };
8
+ });
9
+
10
+ return app;
11
+ }
@@ -0,0 +1,12 @@
1
+ import { buildApp } from "./app.js";
2
+ import { parsePort } from "./parse-port.js";
3
+
4
+ const app = buildApp();
5
+ const port = parsePort(process.env.PORT);
6
+
7
+ try {
8
+ await app.listen({ port, host: "0.0.0.0" });
9
+ } catch (err) {
10
+ app.log.error(err);
11
+ process.exit(1);
12
+ }
@@ -0,0 +1,100 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { parsePort } from "./parse-port.js";
3
+
4
+ describe("parsePort", () => {
5
+ describe("when value is undefined", () => {
6
+ it("should return default port 3000", () => {
7
+ const result = parsePort(undefined);
8
+
9
+ expect(result).toBe(3000);
10
+ });
11
+ });
12
+
13
+ describe("when value is empty string", () => {
14
+ it("should return default port 3000", () => {
15
+ const result = parsePort("");
16
+
17
+ expect(result).toBe(3000);
18
+ });
19
+ });
20
+
21
+ describe("when value is a valid unprivileged port", () => {
22
+ it("should return the parsed port", () => {
23
+ const result = parsePort("8080");
24
+
25
+ expect(result).toBe(8080);
26
+ });
27
+ });
28
+
29
+ describe("when value is invalid", () => {
30
+ let warnSpy: ReturnType<typeof vi.spyOn>;
31
+
32
+ beforeEach(() => {
33
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
34
+ });
35
+
36
+ afterEach(() => {
37
+ warnSpy.mockRestore();
38
+ });
39
+
40
+ it("should warn and return default for non-numeric value", () => {
41
+ const result = parsePort("abc");
42
+
43
+ expect(result).toBe(3000);
44
+ expect(warnSpy).toHaveBeenCalledWith(
45
+ 'Invalid PORT "abc", using default 3000',
46
+ );
47
+ });
48
+
49
+ it("should warn and return default for privileged port", () => {
50
+ const result = parsePort("80");
51
+
52
+ expect(result).toBe(3000);
53
+ expect(warnSpy).toHaveBeenCalledWith(
54
+ 'Invalid PORT "80", using default 3000',
55
+ );
56
+ });
57
+
58
+ it("should warn and return default for port above max", () => {
59
+ const result = parsePort("70000");
60
+
61
+ expect(result).toBe(3000);
62
+ expect(warnSpy).toHaveBeenCalledWith(
63
+ 'Invalid PORT "70000", using default 3000',
64
+ );
65
+ });
66
+
67
+ it("should warn and return default for non-integer port", () => {
68
+ const result = parsePort("3000.5");
69
+
70
+ expect(result).toBe(3000);
71
+ expect(warnSpy).toHaveBeenCalledWith(
72
+ 'Invalid PORT "3000.5", using default 3000',
73
+ );
74
+ });
75
+ });
76
+
77
+ describe("when value is not provided or empty", () => {
78
+ let warnSpy: ReturnType<typeof vi.spyOn>;
79
+
80
+ beforeEach(() => {
81
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
82
+ });
83
+
84
+ afterEach(() => {
85
+ warnSpy.mockRestore();
86
+ });
87
+
88
+ it("should not warn when value is undefined", () => {
89
+ parsePort(undefined);
90
+
91
+ expect(warnSpy).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it("should not warn when value is empty string", () => {
95
+ parsePort("");
96
+
97
+ expect(warnSpy).not.toHaveBeenCalled();
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,20 @@
1
+ const defaultPort = 3000;
2
+ const minUnprivilegedPort = 1024;
3
+ const maxPort = 65535;
4
+
5
+ export function parsePort(value: string | undefined): number {
6
+ if (value === undefined || value === "") {
7
+ return defaultPort;
8
+ }
9
+ const parsed = Number(value);
10
+ if (
11
+ !Number.isInteger(parsed) ||
12
+ parsed < minUnprivilegedPort ||
13
+ parsed > maxPort
14
+ ) {
15
+ // biome-ignore lint/suspicious/noConsole: user-facing warning for invalid PORT
16
+ console.warn(`Invalid PORT "${value}", using default ${defaultPort}`);
17
+ return defaultPort;
18
+ }
19
+ return parsed;
20
+ }
@@ -13,7 +13,7 @@
13
13
  "lousy-agents": {
14
14
  "type": "stdio",
15
15
  "command": "npx",
16
- "args": ["-y", "-p", "@lousy-agents/cli", "lousy-agents-mcp"]
16
+ "args": ["-y", "-p", "@lousy-agents/mcp", "lousy-agents-mcp"]
17
17
  }
18
18
  }
19
19
  }
package/dist/index.js CHANGED
@@ -35198,7 +35198,38 @@ const CLI_TEMPLATE_DIR = (0,external_node_path_.join)(PROJECT_ROOT, "cli", "copi
35198
35198
  path: "vitest.setup.ts",
35199
35199
  content: readRestApiTemplateFile("vitest.setup.ts")
35200
35200
  },
35201
- ...buildCommonNodes(readRestApiTemplateFile)
35201
+ ...buildCommonNodes(readRestApiTemplateFile),
35202
+ // Source code
35203
+ {
35204
+ type: "file",
35205
+ path: "src/app.ts",
35206
+ content: readRestApiTemplateFile("src/app.ts")
35207
+ },
35208
+ {
35209
+ type: "file",
35210
+ path: "src/app.test.ts",
35211
+ content: readRestApiTemplateFile("src/app.test.ts")
35212
+ },
35213
+ {
35214
+ type: "file",
35215
+ path: "src/app.integration.ts",
35216
+ content: readRestApiTemplateFile("src/app.integration.ts")
35217
+ },
35218
+ {
35219
+ type: "file",
35220
+ path: "src/parse-port.ts",
35221
+ content: readRestApiTemplateFile("src/parse-port.ts")
35222
+ },
35223
+ {
35224
+ type: "file",
35225
+ path: "src/parse-port.test.ts",
35226
+ content: readRestApiTemplateFile("src/parse-port.test.ts")
35227
+ },
35228
+ {
35229
+ type: "file",
35230
+ path: "src/index.ts",
35231
+ content: readRestApiTemplateFile("src/index.ts")
35232
+ }
35202
35233
  ]
35203
35234
  };
35204
35235
  return cachedRestApiStructure;
@@ -55779,6 +55810,84 @@ const remark = unified().use(remarkParse).use(remarkStringify).freeze()
55779
55810
  }
55780
55811
  }
55781
55812
 
55813
+ ;// CONCATENATED MODULE: ../core/src/use-cases/apply-severity-filter.ts
55814
+ /**
55815
+ * Shared severity filtering for lint outputs.
55816
+ * Applies rule severity configuration to diagnostics, filtering out "off" rules,
55817
+ * remapping "warn" → "warning", and recalculating summary counts.
55818
+ */ /** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
55819
+ skill: "skills",
55820
+ agent: "agents",
55821
+ instruction: "instructions"
55822
+ };
55823
+ /**
55824
+ * Maps config severity to diagnostic severity.
55825
+ * "warn" → "warning", "error" → "error", "off" → null (drop).
55826
+ */ function apply_severity_filter_mapSeverity(configSeverity) {
55827
+ if (configSeverity === "off") {
55828
+ return null;
55829
+ }
55830
+ if (configSeverity === "warn") {
55831
+ return "warning";
55832
+ }
55833
+ return configSeverity;
55834
+ }
55835
+ /**
55836
+ * Filters instruction suggestions based on rule severity configuration.
55837
+ * Drops suggestions whose corresponding rule is "off".
55838
+ * Suggestions without a ruleId pass through unchanged.
55839
+ */ function filterInstructionSuggestions(suggestions, rules) {
55840
+ return suggestions.filter((suggestion)=>{
55841
+ if (!suggestion.ruleId) {
55842
+ return true;
55843
+ }
55844
+ return rules[suggestion.ruleId] !== "off";
55845
+ });
55846
+ }
55847
+ /**
55848
+ * Applies severity filtering to a LintOutput based on rule configuration.
55849
+ * Drops diagnostics for "off" rules, remaps severity for "warn"/"error" rules.
55850
+ * Diagnostics without a ruleId pass through unchanged.
55851
+ * For instruction targets, also filters qualityResult.suggestions.
55852
+ */ function applySeverityFilter(output, rulesConfig) {
55853
+ const configKey = TARGET_TO_CONFIG_KEY[output.target];
55854
+ const targetRules = rulesConfig[configKey];
55855
+ const filteredDiagnostics = [];
55856
+ for (const diagnostic of output.diagnostics){
55857
+ const configuredSeverity = diagnostic.ruleId ? targetRules[diagnostic.ruleId] : undefined;
55858
+ if (!configuredSeverity) {
55859
+ filteredDiagnostics.push(diagnostic);
55860
+ continue;
55861
+ }
55862
+ const mappedSeverity = apply_severity_filter_mapSeverity(configuredSeverity);
55863
+ if (mappedSeverity === null) {
55864
+ continue;
55865
+ }
55866
+ filteredDiagnostics.push({
55867
+ ...diagnostic,
55868
+ severity: mappedSeverity
55869
+ });
55870
+ }
55871
+ const totalErrors = filteredDiagnostics.filter((d)=>d.severity === "error").length;
55872
+ const totalWarnings = filteredDiagnostics.filter((d)=>d.severity === "warning").length;
55873
+ const totalInfos = filteredDiagnostics.filter((d)=>d.severity === "info").length;
55874
+ const filteredQualityResult = output.qualityResult && configKey === "instructions" ? {
55875
+ ...output.qualityResult,
55876
+ suggestions: filterInstructionSuggestions(output.qualityResult.suggestions, targetRules)
55877
+ } : output.qualityResult;
55878
+ return {
55879
+ ...output,
55880
+ diagnostics: filteredDiagnostics,
55881
+ qualityResult: filteredQualityResult,
55882
+ summary: {
55883
+ ...output.summary,
55884
+ totalErrors,
55885
+ totalWarnings,
55886
+ totalInfos
55887
+ }
55888
+ };
55889
+ }
55890
+
55782
55891
  ;// CONCATENATED MODULE: ../core/src/use-cases/lint-agent-frontmatter.ts
55783
55892
  /**
55784
55893
  * Use case for linting GitHub Copilot custom agent frontmatter.
@@ -56080,6 +56189,7 @@ function hasFrontmatterDelimiters(content) {
56080
56189
 
56081
56190
 
56082
56191
 
56192
+
56083
56193
  /** Schema for validating target directory */ const TargetDirSchema = schemas_string().min(1, "Target directory is required");
56084
56194
  /**
56085
56195
  * Validates the target directory.
@@ -56244,78 +56354,6 @@ function hasFrontmatterDelimiters(content) {
56244
56354
  consola.warn(suggestion.message);
56245
56355
  }
56246
56356
  }
56247
- /** Maps a lint target to its config key */ const TARGET_TO_CONFIG_KEY = {
56248
- skill: "skills",
56249
- agent: "agents",
56250
- instruction: "instructions"
56251
- };
56252
- /**
56253
- * Maps config-facing severity to diagnostic-facing severity.
56254
- * "warn" → "warning", "error" → "error", "off" → null (drop).
56255
- */ function lint_mapSeverity(configSeverity) {
56256
- if (configSeverity === "off") {
56257
- return null;
56258
- }
56259
- if (configSeverity === "warn") {
56260
- return "warning";
56261
- }
56262
- return configSeverity;
56263
- }
56264
- /**
56265
- * Filters instruction suggestions based on rule severity configuration.
56266
- * Drops suggestions whose corresponding rule is "off".
56267
- * Suggestions without a ruleId pass through unchanged.
56268
- */ function filterInstructionSuggestions(suggestions, rules) {
56269
- return suggestions.filter((suggestion)=>{
56270
- if (!suggestion.ruleId) {
56271
- return true;
56272
- }
56273
- return rules[suggestion.ruleId] !== "off";
56274
- });
56275
- }
56276
- /**
56277
- * Applies severity filtering to a LintOutput based on rule configuration.
56278
- * Drops diagnostics for "off" rules, remaps severity for "warn"/"error" rules.
56279
- * Diagnostics without a ruleId pass through unchanged.
56280
- * For instruction targets, also filters qualityResult.suggestions.
56281
- */ function applySeverityFilter(output, rulesConfig) {
56282
- const configKey = TARGET_TO_CONFIG_KEY[output.target];
56283
- const targetRules = rulesConfig[configKey];
56284
- const filteredDiagnostics = [];
56285
- for (const diagnostic of output.diagnostics){
56286
- const configuredSeverity = diagnostic.ruleId ? targetRules[diagnostic.ruleId] : undefined;
56287
- if (!configuredSeverity) {
56288
- filteredDiagnostics.push(diagnostic);
56289
- continue;
56290
- }
56291
- const mappedSeverity = lint_mapSeverity(configuredSeverity);
56292
- if (mappedSeverity === null) {
56293
- continue;
56294
- }
56295
- filteredDiagnostics.push({
56296
- ...diagnostic,
56297
- severity: mappedSeverity
56298
- });
56299
- }
56300
- const totalErrors = filteredDiagnostics.filter((d)=>d.severity === "error").length;
56301
- const totalWarnings = filteredDiagnostics.filter((d)=>d.severity === "warning").length;
56302
- const totalInfos = filteredDiagnostics.filter((d)=>d.severity === "info").length;
56303
- const filteredQualityResult = output.qualityResult && configKey === "instructions" ? {
56304
- ...output.qualityResult,
56305
- suggestions: filterInstructionSuggestions(output.qualityResult.suggestions, targetRules)
56306
- } : output.qualityResult;
56307
- return {
56308
- ...output,
56309
- diagnostics: filteredDiagnostics,
56310
- qualityResult: filteredQualityResult,
56311
- summary: {
56312
- ...output.summary,
56313
- totalErrors,
56314
- totalWarnings,
56315
- totalInfos
56316
- }
56317
- };
56318
- }
56319
56357
  /**
56320
56358
  * The `lint` command for validating agent skills, custom agents, and instruction files.
56321
56359
  */ const lintCommand = defineCommand({