specguard-mcp 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/README.md +366 -0
- package/dist/bin/specguard-mcp.d.ts +2 -0
- package/dist/bin/specguard-mcp.js +35 -0
- package/dist/bin/specguard-mcp.js.map +1 -0
- package/dist/src/config.d.ts +108 -0
- package/dist/src/config.js +172 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/errors.d.ts +60 -0
- package/dist/src/errors.js +64 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/server.d.ts +28 -0
- package/dist/src/server.js +113 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/support/run-command.d.ts +86 -0
- package/dist/src/support/run-command.js +322 -0
- package/dist/src/support/run-command.js.map +1 -0
- package/dist/src/support/specguard-api.d.ts +11 -0
- package/dist/src/support/specguard-api.js +157 -0
- package/dist/src/support/specguard-api.js.map +1 -0
- package/dist/src/tools/args.d.ts +48 -0
- package/dist/src/tools/args.js +66 -0
- package/dist/src/tools/args.js.map +1 -0
- package/dist/src/tools/index.d.ts +33 -0
- package/dist/src/tools/index.js +34 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/lint-intent-annotations.d.ts +45 -0
- package/dist/src/tools/lint-intent-annotations.js +342 -0
- package/dist/src/tools/lint-intent-annotations.js.map +1 -0
- package/dist/src/tools/repository-overview.d.ts +424 -0
- package/dist/src/tools/repository-overview.js +797 -0
- package/dist/src/tools/repository-overview.js.map +1 -0
- package/dist/src/tools/types.d.ts +111 -0
- package/dist/src/tools/types.js +2 -0
- package/dist/src/tools/types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ToolDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* THE REGISTRY — the one file that changes when the toolset grows.
|
|
4
|
+
*
|
|
5
|
+
* SPGD-310 ships a bootstrap, not a tool contract: the initial set wraps
|
|
6
|
+
* capabilities that are verified-shipped today, and the rest fills in as more
|
|
7
|
+
* of SpecGuard lands. This array is the seam that makes that true. `server.ts`
|
|
8
|
+
* iterates it and has no knowledge of any individual tool, so adding one is a
|
|
9
|
+
* new file plus one line here — no new plumbing, no `switch` to extend, no
|
|
10
|
+
* change to how the server starts, authenticates or reports errors.
|
|
11
|
+
*
|
|
12
|
+
* == What is in the bootstrap, and why only these two
|
|
13
|
+
*
|
|
14
|
+
* - `lint_intent_annotations` wraps `specguard-lint` (shipped:
|
|
15
|
+
* `specguard-rspec/bin/specguard-lint`, SPGD-12 §1).
|
|
16
|
+
* - `get_repository_overview` wraps `GET /api/v1/repository` (shipped:
|
|
17
|
+
* `specguard/config/routes.rb`).
|
|
18
|
+
*
|
|
19
|
+
* Both were confirmed present before being wrapped, and the two chosen cover
|
|
20
|
+
* the two halves of the surface — one local subprocess, one authenticated HTTP
|
|
21
|
+
* call — so the shape is proven on both kinds of capability rather than on one.
|
|
22
|
+
*
|
|
23
|
+
* == What is deliberately absent
|
|
24
|
+
*
|
|
25
|
+
* `/check-intent` and duplicate clustering are NOT here and must not be added
|
|
26
|
+
* until their backing engine and data exist (SPGD-114 / SPGD-115). A tool
|
|
27
|
+
* advertised in `tools/list` is a promise an agent will act on: wrapping an
|
|
28
|
+
* endpoint that does not exist would produce a server that discovers cleanly
|
|
29
|
+
* and fails on use, which is worse than not offering the tool, because the
|
|
30
|
+
* agent has already committed to a plan by the time it finds out.
|
|
31
|
+
*/
|
|
32
|
+
export declare const TOOLS: readonly ToolDefinition[];
|
|
33
|
+
export type { ToolContext, ToolDefinition, ToolResult } from "./types.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import lintIntentAnnotations from "./lint-intent-annotations.js";
|
|
2
|
+
import getRepositoryOverview from "./repository-overview.js";
|
|
3
|
+
/**
|
|
4
|
+
* THE REGISTRY — the one file that changes when the toolset grows.
|
|
5
|
+
*
|
|
6
|
+
* SPGD-310 ships a bootstrap, not a tool contract: the initial set wraps
|
|
7
|
+
* capabilities that are verified-shipped today, and the rest fills in as more
|
|
8
|
+
* of SpecGuard lands. This array is the seam that makes that true. `server.ts`
|
|
9
|
+
* iterates it and has no knowledge of any individual tool, so adding one is a
|
|
10
|
+
* new file plus one line here — no new plumbing, no `switch` to extend, no
|
|
11
|
+
* change to how the server starts, authenticates or reports errors.
|
|
12
|
+
*
|
|
13
|
+
* == What is in the bootstrap, and why only these two
|
|
14
|
+
*
|
|
15
|
+
* - `lint_intent_annotations` wraps `specguard-lint` (shipped:
|
|
16
|
+
* `specguard-rspec/bin/specguard-lint`, SPGD-12 §1).
|
|
17
|
+
* - `get_repository_overview` wraps `GET /api/v1/repository` (shipped:
|
|
18
|
+
* `specguard/config/routes.rb`).
|
|
19
|
+
*
|
|
20
|
+
* Both were confirmed present before being wrapped, and the two chosen cover
|
|
21
|
+
* the two halves of the surface — one local subprocess, one authenticated HTTP
|
|
22
|
+
* call — so the shape is proven on both kinds of capability rather than on one.
|
|
23
|
+
*
|
|
24
|
+
* == What is deliberately absent
|
|
25
|
+
*
|
|
26
|
+
* `/check-intent` and duplicate clustering are NOT here and must not be added
|
|
27
|
+
* until their backing engine and data exist (SPGD-114 / SPGD-115). A tool
|
|
28
|
+
* advertised in `tools/list` is a promise an agent will act on: wrapping an
|
|
29
|
+
* endpoint that does not exist would produce a server that discovers cleanly
|
|
30
|
+
* and fails on use, which is worse than not offering the tool, because the
|
|
31
|
+
* agent has already committed to a plan by the time it finds out.
|
|
32
|
+
*/
|
|
33
|
+
export const TOOLS = [lintIntentAnnotations, getRepositoryOverview];
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,MAAM,8BAA8B,CAAC;AACjE,OAAO,qBAAqB,MAAM,0BAA0B,CAAC;AAG7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,KAAK,GAA8B,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ToolDefinition } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* `specguard-lint --json` as a tool — the `@intent` linter, which is shipped
|
|
4
|
+
* today in `specguard-rspec` (`bin/specguard-lint`, SPGD-12 §1).
|
|
5
|
+
*
|
|
6
|
+
* == Why this wraps the JSON renderer and not the human one
|
|
7
|
+
*
|
|
8
|
+
* The gem grew `--json` in SPGD-305 precisely so a consumer gets *which file,
|
|
9
|
+
* which line, which rule* as data instead of a prose format to regex. An agent
|
|
10
|
+
* is that consumer. Passing `--json` unconditionally is what makes this a thin
|
|
11
|
+
* client rather than a parser: the document goes back as the linter emitted it,
|
|
12
|
+
* key for key, and this file contains no knowledge of the OpenTestIntent schema,
|
|
13
|
+
* no finding shape, and no rule about what makes an annotation valid.
|
|
14
|
+
*
|
|
15
|
+
* == The exit code is a verdict, not an error
|
|
16
|
+
*
|
|
17
|
+
* The linter's contract is three-valued and the mapping is the one decision
|
|
18
|
+
* this tool actually makes:
|
|
19
|
+
*
|
|
20
|
+
* 0 — every annotation checked was valid (including "there were none").
|
|
21
|
+
* 1 — at least one annotation is malformed. A SUCCESSFUL tool call with
|
|
22
|
+
* findings in it. Reporting exit 1 as a tool error would tell the agent
|
|
23
|
+
* the linter broke, when what happened is the linter worked and the code
|
|
24
|
+
* is wrong — and an agent that reads "tool failed" retries the tool
|
|
25
|
+
* instead of fixing the annotation.
|
|
26
|
+
* 2 — the linter could not do its job. A tool error, and the only one. The
|
|
27
|
+
* gem deliberately emits NO document on this path (a `{"ok": false,
|
|
28
|
+
* "findings": []}` for a run that checked nothing is how a gate that
|
|
29
|
+
* checked nothing gets mistaken for one that found nothing), so the prose
|
|
30
|
+
* it wrote to stderr is the entire diagnosis and is passed through.
|
|
31
|
+
*
|
|
32
|
+
* `ok` is read off the document rather than re-derived from the exit code, and
|
|
33
|
+
* the exit code is reported beside it, so an agent can see the two agree.
|
|
34
|
+
*
|
|
35
|
+
* == What is deliberately not validated here
|
|
36
|
+
*
|
|
37
|
+
* `--changed` together with explicit paths is misuse, and the linter exits 2
|
|
38
|
+
* saying so. This tool does not pre-empt that. Re-implementing the rule would
|
|
39
|
+
* put a second copy of the linter's argument grammar in a different language,
|
|
40
|
+
* free to drift the day the gem adds a flag — and the tool would then be
|
|
41
|
+
* enforcing a contract it does not own. The schema documents the conflict; the
|
|
42
|
+
* linter adjudicates it.
|
|
43
|
+
*/
|
|
44
|
+
declare const lintIntentAnnotations: ToolDefinition;
|
|
45
|
+
export default lintIntentAnnotations;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { ArgumentError, CommandError } from "../errors.js";
|
|
4
|
+
import { EXIT_CLOSE_GRACE_MS, MAX_OUTPUT_BYTES } from "../support/run-command.js";
|
|
5
|
+
import { optionalBoolean, optionalString } from "./args.js";
|
|
6
|
+
/**
|
|
7
|
+
* `specguard-lint --json` as a tool — the `@intent` linter, which is shipped
|
|
8
|
+
* today in `specguard-rspec` (`bin/specguard-lint`, SPGD-12 §1).
|
|
9
|
+
*
|
|
10
|
+
* == Why this wraps the JSON renderer and not the human one
|
|
11
|
+
*
|
|
12
|
+
* The gem grew `--json` in SPGD-305 precisely so a consumer gets *which file,
|
|
13
|
+
* which line, which rule* as data instead of a prose format to regex. An agent
|
|
14
|
+
* is that consumer. Passing `--json` unconditionally is what makes this a thin
|
|
15
|
+
* client rather than a parser: the document goes back as the linter emitted it,
|
|
16
|
+
* key for key, and this file contains no knowledge of the OpenTestIntent schema,
|
|
17
|
+
* no finding shape, and no rule about what makes an annotation valid.
|
|
18
|
+
*
|
|
19
|
+
* == The exit code is a verdict, not an error
|
|
20
|
+
*
|
|
21
|
+
* The linter's contract is three-valued and the mapping is the one decision
|
|
22
|
+
* this tool actually makes:
|
|
23
|
+
*
|
|
24
|
+
* 0 — every annotation checked was valid (including "there were none").
|
|
25
|
+
* 1 — at least one annotation is malformed. A SUCCESSFUL tool call with
|
|
26
|
+
* findings in it. Reporting exit 1 as a tool error would tell the agent
|
|
27
|
+
* the linter broke, when what happened is the linter worked and the code
|
|
28
|
+
* is wrong — and an agent that reads "tool failed" retries the tool
|
|
29
|
+
* instead of fixing the annotation.
|
|
30
|
+
* 2 — the linter could not do its job. A tool error, and the only one. The
|
|
31
|
+
* gem deliberately emits NO document on this path (a `{"ok": false,
|
|
32
|
+
* "findings": []}` for a run that checked nothing is how a gate that
|
|
33
|
+
* checked nothing gets mistaken for one that found nothing), so the prose
|
|
34
|
+
* it wrote to stderr is the entire diagnosis and is passed through.
|
|
35
|
+
*
|
|
36
|
+
* `ok` is read off the document rather than re-derived from the exit code, and
|
|
37
|
+
* the exit code is reported beside it, so an agent can see the two agree.
|
|
38
|
+
*
|
|
39
|
+
* == What is deliberately not validated here
|
|
40
|
+
*
|
|
41
|
+
* `--changed` together with explicit paths is misuse, and the linter exits 2
|
|
42
|
+
* saying so. This tool does not pre-empt that. Re-implementing the rule would
|
|
43
|
+
* put a second copy of the linter's argument grammar in a different language,
|
|
44
|
+
* free to drift the day the gem adds a flag — and the tool would then be
|
|
45
|
+
* enforcing a contract it does not own. The schema documents the conflict; the
|
|
46
|
+
* linter adjudicates it.
|
|
47
|
+
*/
|
|
48
|
+
const lintIntentAnnotations = {
|
|
49
|
+
name: "lint_intent_annotations",
|
|
50
|
+
title: "Lint @intent annotations",
|
|
51
|
+
description: "Validate the `@intent:` annotations in a Ruby project's RSpec files against the " +
|
|
52
|
+
"OpenTestIntent schema, using that project's own `specguard-lint`. Returns each finding as " +
|
|
53
|
+
"structured data — file, line, failure kind (schema / extraction / parse / read), and every " +
|
|
54
|
+
"violated rule — so a malformed annotation can be fixed without reading a CI log. " +
|
|
55
|
+
"Use it after writing or editing `@intent:` annotations, or to audit a suite. " +
|
|
56
|
+
"A MISSING annotation is never a failure: adoption is gradual by design, so a suite with no " +
|
|
57
|
+
"annotations at all lints clean. Runs locally and needs no SpecGuard deployment or API key.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
project_dir: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Absolute path to the Ruby project to lint — the directory holding its Gemfile and " +
|
|
64
|
+
"spec/. Defaults to the directory this server was started in.",
|
|
65
|
+
},
|
|
66
|
+
paths: {
|
|
67
|
+
type: "array",
|
|
68
|
+
items: { type: "string" },
|
|
69
|
+
description: "Specific spec files to check, relative to project_dir. Omit to check every *_spec.rb " +
|
|
70
|
+
"under it. An empty list is an error, not a way to say 'everything'. " +
|
|
71
|
+
"Cannot be combined with `changed`.",
|
|
72
|
+
},
|
|
73
|
+
changed: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
description: "Check only spec files that differ from the merge base with the default branch — the " +
|
|
76
|
+
"mode CI uses. Cannot be combined with `paths`.",
|
|
77
|
+
},
|
|
78
|
+
base: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Diff `changed` against this git ref instead of the default merge base. Ignored unless " +
|
|
81
|
+
"`changed` is true.",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
additionalProperties: false,
|
|
85
|
+
},
|
|
86
|
+
async run(args, context) {
|
|
87
|
+
const projectDir = optionalString(args["project_dir"], "project_dir");
|
|
88
|
+
const paths = optionalStringArray(args["paths"], "paths");
|
|
89
|
+
const changed = optionalBoolean(args["changed"], "changed");
|
|
90
|
+
const base = optionalString(args["base"], "base");
|
|
91
|
+
// Every argument is checked before anything is spawned, so a call that was
|
|
92
|
+
// never going to run cannot leave a subprocess behind to explain.
|
|
93
|
+
if (projectDir !== undefined)
|
|
94
|
+
await requireProjectDir(projectDir);
|
|
95
|
+
const argv = [...context.config.lintCommand, "--json"];
|
|
96
|
+
if (changed === true)
|
|
97
|
+
argv.push(base === undefined ? "--changed" : `--changed=${base}`);
|
|
98
|
+
if (paths !== undefined)
|
|
99
|
+
argv.push(...paths);
|
|
100
|
+
const result = await context.runCommand(argv, {
|
|
101
|
+
cwd: projectDir,
|
|
102
|
+
notFoundHint: LINT_COMMAND_HINT,
|
|
103
|
+
});
|
|
104
|
+
// Exit 2 — and every signal death, which is the same "no verdict" outcome
|
|
105
|
+
// wearing a different code. stderr is the diagnosis; there is no document.
|
|
106
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
107
|
+
throw new CommandError(`specguard-lint could not check anything (exit ${result.code ?? `signal ${result.signal}`}). ` +
|
|
108
|
+
`It reported:\n${result.stderr.trim() || "(nothing on stderr)"}`);
|
|
109
|
+
}
|
|
110
|
+
const report = parseReport(result);
|
|
111
|
+
return {
|
|
112
|
+
// The provenance line the gem writes to stderr on EVERY run names which
|
|
113
|
+
// implementation produced the verdicts (Ruby, or the Go port when
|
|
114
|
+
// SPECGUARD_VALIDATE_INTENT is set). It is deliberately absent from the
|
|
115
|
+
// document — SPGD-247 keeps it in one place — so dropping stderr here
|
|
116
|
+
// would make "which validator ran" unanswerable from this tool.
|
|
117
|
+
text: renderText(report, result.stderr, result.code),
|
|
118
|
+
structured: {
|
|
119
|
+
exit_code: result.code,
|
|
120
|
+
ok: report.ok,
|
|
121
|
+
report,
|
|
122
|
+
linter_stderr: result.stderr.trim(),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
export default lintIntentAnnotations;
|
|
128
|
+
/**
|
|
129
|
+
* What to try when `specguard-lint` is not on PATH.
|
|
130
|
+
*
|
|
131
|
+
* Passed to `runCommand` rather than baked into it: only this tool knows that
|
|
132
|
+
* ITS command comes from `SPECGUARD_LINT_COMMAND`, and a shared helper that
|
|
133
|
+
* names one tool's variable would misdirect every later tool that spawns
|
|
134
|
+
* something else.
|
|
135
|
+
*/
|
|
136
|
+
const LINT_COMMAND_HINT = "Set SPECGUARD_LINT_COMMAND to the command that runs it here " +
|
|
137
|
+
'(for a bundled Ruby project that is usually "bundle exec specguard-lint").';
|
|
138
|
+
/**
|
|
139
|
+
* `project_dir` names a directory that exists, or the failure says exactly that.
|
|
140
|
+
*
|
|
141
|
+
* This check earns its place because of WHO supplies the argument. `project_dir`
|
|
142
|
+
* is model-supplied — an agent guesses a repository root — which makes it the
|
|
143
|
+
* likeliest thing about a call to this tool to be wrong. Unchecked it becomes
|
|
144
|
+
* `spawn`'s `cwd`, and Node reports a missing `cwd` as ENOENT: the same code as
|
|
145
|
+
* a missing binary. The answer to a mistyped path would then be "specguard-lint
|
|
146
|
+
* is not on this server's PATH; set SPECGUARD_LINT_COMMAND" — every clause of it
|
|
147
|
+
* wrong, and it names the one thing that was right. Worse, it points an agent at
|
|
148
|
+
* MCP server configuration it usually cannot reach, to fix a working install, so
|
|
149
|
+
* it will try again and fail identically.
|
|
150
|
+
*
|
|
151
|
+
* Checked here rather than in `run-command` because this is the only place the
|
|
152
|
+
* argument's NAME is known, and naming `project_dir` is what turns this from a
|
|
153
|
+
* message about the server into one the agent can act on unaided.
|
|
154
|
+
*/
|
|
155
|
+
async function requireProjectDir(projectDir) {
|
|
156
|
+
let isDirectory;
|
|
157
|
+
try {
|
|
158
|
+
isDirectory = (await stat(projectDir)).isDirectory();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
throw new CommandError(`\`project_dir\` ${JSON.stringify(projectDir)} does not exist${resolvedNote(projectDir)}. ` +
|
|
162
|
+
"Pass the absolute path of the project root — the directory holding its Gemfile and " +
|
|
163
|
+
"spec/ — or omit `project_dir` to lint the directory this server was started in. " +
|
|
164
|
+
"(specguard-lint itself was not the problem.)");
|
|
165
|
+
}
|
|
166
|
+
if (!isDirectory) {
|
|
167
|
+
throw new CommandError(`\`project_dir\` ${JSON.stringify(projectDir)} is not a directory${resolvedNote(projectDir)}. ` +
|
|
168
|
+
"Pass the project root — the directory holding its Gemfile and spec/ — not a file inside " +
|
|
169
|
+
"it. To lint one file, pass it in `paths` instead.");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A relative `project_dir` is resolved against whatever directory this server
|
|
174
|
+
* was started in, which is not the agent's working directory and is not
|
|
175
|
+
* something the agent can see. When the two can differ, the path that was
|
|
176
|
+
* actually used is the useful half of the message.
|
|
177
|
+
*/
|
|
178
|
+
function resolvedNote(projectDir) {
|
|
179
|
+
return isAbsolute(projectDir)
|
|
180
|
+
? ""
|
|
181
|
+
: ` (a relative path, resolved against this server's working directory to ${JSON.stringify(resolve(projectDir))})`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Takes the whole `CommandResult` rather than the three or four fields it reads.
|
|
185
|
+
*
|
|
186
|
+
* Not tidiness: `stdoutTruncated` and `outputDrained` are both booleans meaning
|
|
187
|
+
* "the output is not all here", they are both consulted below, and they carry
|
|
188
|
+
* OPPOSITE remedies — ask for less output, versus stop leaving a process holding
|
|
189
|
+
* the pipe. As positional arguments they would typecheck swapped, and a swap
|
|
190
|
+
* would silently exchange one of this file's diagnoses for the other. The struct
|
|
191
|
+
* makes the two unswappable at the call site.
|
|
192
|
+
*/
|
|
193
|
+
function parseReport(result) {
|
|
194
|
+
const { stdout, stderr, stdoutTruncated: truncated, outputDrained: drained } = result;
|
|
195
|
+
const trimmed = stdout.trim();
|
|
196
|
+
if (trimmed === "") {
|
|
197
|
+
// Asked BEFORE the sibling below, because the two answer different
|
|
198
|
+
// questions and only one of them is established here. "The linter wrote no
|
|
199
|
+
// document" is a claim about the linter; when the pipe was never drained
|
|
200
|
+
// this bridge stopped listening, so what the linter wrote is precisely what
|
|
201
|
+
// we do not know. Blaming SPECGUARD_LINT_COMMAND for silence we caused is
|
|
202
|
+
// the `cwd`-vs-PATH mistake this file already refuses once above — it sends
|
|
203
|
+
// the reader to change the one thing that was right.
|
|
204
|
+
if (!drained)
|
|
205
|
+
throw new CommandError(undrainedPipe(result, ""));
|
|
206
|
+
// stderr is carried, not dropped. An exit of 0 or 1 with an empty stdout is
|
|
207
|
+
// most often produced by the WRAPPER in SPECGUARD_LINT_COMMAND — `bundle`,
|
|
208
|
+
// `rbenv`, a docker shim — failing before the linter ever ran, and such a
|
|
209
|
+
// wrapper explains itself on stderr and nowhere else. Withholding that line
|
|
210
|
+
// to speculate about the variable instead leaves the one reader who could
|
|
211
|
+
// fix it guessing, which is the failure mode the sibling branches below
|
|
212
|
+
// (and the exit-2 branch above) already avoid by quoting their evidence.
|
|
213
|
+
const reported = stderr.trim();
|
|
214
|
+
throw new CommandError("specguard-lint exited with a verdict but wrote no JSON document, so there are no " +
|
|
215
|
+
"findings to report — this is NOT a clean run." +
|
|
216
|
+
(reported === ""
|
|
217
|
+
? " It wrote nothing on stderr either. The command in SPECGUARD_LINT_COMMAND may not be " +
|
|
218
|
+
"specguard-lint, or may be a version older than the one that added --json."
|
|
219
|
+
: `\n\nIt reported:\n${truncate(reported)}\n\n` +
|
|
220
|
+
"That is the command in SPECGUARD_LINT_COMMAND reporting why it produced no document."));
|
|
221
|
+
}
|
|
222
|
+
let parsed;
|
|
223
|
+
try {
|
|
224
|
+
parsed = JSON.parse(trimmed);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Truncation is checked FIRST, because it is a cause and "not JSON" is only
|
|
228
|
+
// the symptom it produces. A document this bridge cut in half does not parse,
|
|
229
|
+
// and blaming the linter's output for it would name the wrong cause — and
|
|
230
|
+
// withhold the one thing that would fix it, which is asking for less.
|
|
231
|
+
if (truncated) {
|
|
232
|
+
throw new CommandError(`specguard-lint produced more than ${MAX_OUTPUT_BYTES / 1024 / 1024} MB of output, so ` +
|
|
233
|
+
"this bridge truncated it and the JSON document is incomplete — no findings could be " +
|
|
234
|
+
"read from it. Narrow the run with `paths`, or with `changed` to check only what " +
|
|
235
|
+
"differs from the merge base.");
|
|
236
|
+
}
|
|
237
|
+
// The same reasoning one rung further out, and it is ordered AFTER truncation
|
|
238
|
+
// deliberately: both mean "this bridge stopped reading", but truncation is
|
|
239
|
+
// the definite cut with the actionable remedy, so when a run manages to hit
|
|
240
|
+
// the 4 MB ceiling AND leak a descriptor, "ask for less" is still the advice
|
|
241
|
+
// that works. Below it, though, a document this bridge stopped reading is
|
|
242
|
+
// not the linter emitting garbage, and saying so would name the wrong cause.
|
|
243
|
+
if (!drained)
|
|
244
|
+
throw new CommandError(undrainedPipe(result, trimmed));
|
|
245
|
+
throw new CommandError(`specguard-lint's output was not JSON, so no findings could be read. It wrote:\n${truncate(trimmed)}`);
|
|
246
|
+
}
|
|
247
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
248
|
+
throw new CommandError("specguard-lint's --json output was not a JSON object.");
|
|
249
|
+
}
|
|
250
|
+
return parsed;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The diagnosis for a run whose output this bridge stopped reading.
|
|
254
|
+
*
|
|
255
|
+
* Reached only from the two failure branches above, never on its own: an
|
|
256
|
+
* undrained pipe that still delivered a parseable document is not an error and
|
|
257
|
+
* must not be reported as one. `outputDrained: false` says the tail was not
|
|
258
|
+
* waited for, not that anything is missing from what arrived — the run whose
|
|
259
|
+
* last unread byte was a newline is a successful run, and the caller sees the
|
|
260
|
+
* findings. So this is what a lost tail COST, asked only once something is
|
|
261
|
+
* actually absent.
|
|
262
|
+
*
|
|
263
|
+
* Names the pipe-holder rather than the linter or its configuration, because
|
|
264
|
+
* the remedy lives in neither: the command ran and SPECGUARD_LINT_COMMAND is
|
|
265
|
+
* correct. What is wrong is that the run leaves something behind holding the
|
|
266
|
+
* output, and re-running is the one thing guaranteed not to help.
|
|
267
|
+
*/
|
|
268
|
+
function undrainedPipe(result, partial) {
|
|
269
|
+
const reported = result.stderr.trim();
|
|
270
|
+
return (`specguard-lint exited ${result.code}, but this bridge did not receive its complete JSON ` +
|
|
271
|
+
`document — no findings could be read, and this is NOT a clean run. The command itself ` +
|
|
272
|
+
`finished; something it left running kept the output pipe open, so the bridge stopped ` +
|
|
273
|
+
`reading ${EXIT_CLOSE_GRACE_MS}ms later rather than waiting forever.\n\n` +
|
|
274
|
+
"The linter's output is not the problem here, and neither is SPECGUARD_LINT_COMMAND — that " +
|
|
275
|
+
"command ran. Look for a process the run leaves behind holding its stdout: a preloader " +
|
|
276
|
+
"(Ruby's `spring` is the usual one) or a helper the wrapper starts in the background. " +
|
|
277
|
+
"Stopping it is what makes the document arrive; re-running unchanged will end the same way." +
|
|
278
|
+
(partial === "" ? "" : `\n\nThe partial document was:\n${truncate(partial)}`) +
|
|
279
|
+
(reported === "" ? "" : `\n\nIt reported on stderr:\n${truncate(reported)}`));
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The text rendering, built FROM the same parsed document the structured half
|
|
283
|
+
* carries — never a second pass over the raw output. A tool whose two renderings
|
|
284
|
+
* of one call can disagree is worse than one that returns only prose, because
|
|
285
|
+
* the disagreement is invisible from outside.
|
|
286
|
+
*/
|
|
287
|
+
function renderText(report, stderr, code) {
|
|
288
|
+
const provenance = stderr.trim();
|
|
289
|
+
return [
|
|
290
|
+
`specguard-lint exited ${code} (${code === 0 ? "no malformed annotations" : "malformed annotations found"}).`,
|
|
291
|
+
provenance === "" ? undefined : provenance,
|
|
292
|
+
JSON.stringify(report, null, 2),
|
|
293
|
+
]
|
|
294
|
+
.filter((line) => line !== undefined)
|
|
295
|
+
.join("\n\n");
|
|
296
|
+
}
|
|
297
|
+
function truncate(value, limit = 2000) {
|
|
298
|
+
return value.length <= limit ? value : `${value.slice(0, limit)}… [truncated]`;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* A non-empty list of non-empty paths, or nothing.
|
|
302
|
+
*
|
|
303
|
+
* `paths: []` is REFUSED rather than normalised. Both readings of it are bad and
|
|
304
|
+
* neither is what an agent meant: passed through, an empty list leaves the
|
|
305
|
+
* linter with no positional arguments and it audits EVERY spec file in the
|
|
306
|
+
* project, so "check nothing" comes back as a clean bill of health for the whole
|
|
307
|
+
* suite; silently treated as "not given", the same thing happens by a different
|
|
308
|
+
* route. A tool whose entire purpose is to stop a check that examined nothing
|
|
309
|
+
* from looking like a check that found nothing cannot itself answer an empty
|
|
310
|
+
* selection with "all clean". So it is an error, and the message says which of
|
|
311
|
+
* the two the caller probably wanted.
|
|
312
|
+
*/
|
|
313
|
+
function optionalStringArray(value, field) {
|
|
314
|
+
if (value === undefined || value === null)
|
|
315
|
+
return undefined;
|
|
316
|
+
if (!Array.isArray(value))
|
|
317
|
+
throw new ArgumentError(`\`${field}\` must be an array of strings.`);
|
|
318
|
+
const entries = value.map((entry) => {
|
|
319
|
+
if (typeof entry !== "string")
|
|
320
|
+
throw new ArgumentError(`\`${field}\` must contain only strings.`);
|
|
321
|
+
// Trimmed BEFORE the checks below, so a stray space cannot smuggle an entry
|
|
322
|
+
// past the leading-dash guard as " --version".
|
|
323
|
+
return entry.trim();
|
|
324
|
+
});
|
|
325
|
+
const nonEmpty = entries.filter((entry) => entry !== "");
|
|
326
|
+
if (nonEmpty.length === 0) {
|
|
327
|
+
throw new CommandError(`\`${field}\` was given but names no file${entries.length === 0 ? "" : " (every entry was blank)"}, ` +
|
|
328
|
+
"which selects nothing to check. Omit `paths` to check every spec file in the project, " +
|
|
329
|
+
"or list the files you want checked — an empty selection is not treated as “everything”, " +
|
|
330
|
+
"because a run that checked nothing must never come back clean.");
|
|
331
|
+
}
|
|
332
|
+
// A leading `-` would be read by the linter's OptionParser as a flag rather
|
|
333
|
+
// than a path — `--version` would exit 0 having checked nothing, which is the
|
|
334
|
+
// false-clean result this whole toolchain exists to prevent. Paths are paths.
|
|
335
|
+
for (const entry of nonEmpty) {
|
|
336
|
+
if (entry.startsWith("-")) {
|
|
337
|
+
throw new CommandError(`\`${field}\` entries are file paths and cannot start with "-" (got ${JSON.stringify(entry)}).`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return nonEmpty;
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=lint-intent-annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint-intent-annotations.js","sourceRoot":"","sources":["../../../src/tools/lint-intent-annotations.ts"],"names":[],"mappings":"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"}
|