@stsepelin/checktrail 0.1.0-alpha.1 → 0.1.0-alpha.3

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 CHANGED
@@ -4,18 +4,28 @@ Local code validation with a CLI, MCP tools, and evidence of what actually ran.
4
4
 
5
5
  Formerly Repo Verifier. See the [rename guide](docs/RENAMING.md) for existing source checkouts.
6
6
 
7
- **Experimental preview: 0.1.0-alpha.1.** Public source is available at
7
+ **Published preview: 0.1.0-alpha.2; source candidate: 0.1.0-alpha.3.** Public source is available at
8
8
  [stsepelin/checktrail](https://github.com/stsepelin/checktrail).
9
9
  See [implementation status](docs/STATUS.md), the [plan](docs/PLAN.md) and the
10
10
  [language matrix](docs/LANGUAGES.md) before relying on an adapter.
11
11
  [Installation](docs/INSTALLATION.md) covers the CLI, Claude Code and Codex.
12
+ [Agent skills](docs/SKILLS.md) provides setup, validation and review workflows
13
+ installable with `npx skills add stsepelin/checktrail`.
14
+ [Project setup](docs/ONBOARDING.md) covers `init`, `doctor`
15
+ and MCP configuration generator.
12
16
  [Release preparation](docs/RELEASE.md) records publication and verification gates. The
13
17
  [milestone audit](docs/ACCEPTANCE.md) separates implemented profiles from open
14
18
  acceptance work; [client checks](docs/CLIENTS.md) record actual application coverage.
19
+ [Public adoption](docs/PUBLIC-ADOPTION.md) records the published package on five
20
+ pinned libraries, including setup friction and compatibility gaps.
21
+ The alpha.3 candidate fixes the recorded [TypeScript 4.9.5 incompatibility](docs/TYPESCRIPT.md);
22
+ it has not been published.
15
23
 
16
- The [13-job hosted matrix](https://github.com/stsepelin/checktrail/actions/runs/35573066804)
17
- passed at `52ba415` on Linux and macOS. This identifies a verified source revision;
18
- it does not imply that a later package version has been published.
24
+ The [hosted matrix](https://github.com/stsepelin/checktrail/actions/runs/35590670960)
25
+ passed at release commit `4ce8398` on Linux and macOS. The exact npm tarball and
26
+ fresh CLI/MCP installations were verified. The
27
+ [MCP Registry entry](https://registry.modelcontextprotocol.io/v0.1/servers/io.github.stsepelin%2Fchecktrail/versions/0.1.0-alpha.2)
28
+ is active; execution remains disabled by default.
19
29
 
20
30
  Checktrail discovers projects, plans registered checks, invokes native tools
21
31
  when explicitly trusted, and reports results without turning skipped or empty
@@ -41,7 +51,7 @@ node dist/src/cli.js run --root examples/javascript --trust-project --detailed
41
51
  `--trust-project`: tests, compiler plugins and project configuration can execute
42
52
  code with your user privileges. This is not a sandbox.
43
53
 
44
- Commands always return JSON except help/version. Exit codes:
54
+ Successful commands return JSON except help/version; input errors use stderr. Exit codes:
45
55
 
46
56
  | Code | Meaning |
47
57
  | ---- | --------------------------------------------------------------------------------------- |
package/dist/src/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
+ import { initialize, diagnose, mcpConfiguration, mcpClients, } from "./onboarding.js";
3
4
  import { createReviewContext, projectReviewContext, receiveReview, projectReviewReceipt, } from "./review.js";
4
5
  import { fetchPolicyPack } from "./fetch-pack.js";
5
6
  import { externalReferencesSchema, loadExternalAdapters, } from "./external-adapter.js";
@@ -22,11 +23,15 @@ import { VERSION } from "./types.js";
22
23
  import { validateContracts, projectContractReport } from "./contracts.js";
23
24
  import { compareRuntimeInventories, projectRuntimeComparison, } from "./runtime-inventory.js";
24
25
  async function main() {
25
- const { values, positionals } = parseArgs({
26
+ const { values, positionals, tokens } = parseArgs({
27
+ tokens: true,
26
28
  allowPositionals: true,
27
29
  strict: true,
28
30
  options: {
29
31
  root: { type: "string", default: "." },
32
+ write: { type: "boolean", default: false },
33
+ check: { type: "string", multiple: true },
34
+ client: { type: "string" },
30
35
  "trust-project": { type: "boolean", default: false },
31
36
  "allow-execution": { type: "boolean", default: false },
32
37
  detailed: { type: "boolean", default: false },
@@ -58,12 +63,29 @@ async function main() {
58
63
  return;
59
64
  }
60
65
  if (values.help || positionals.length === 0) {
61
- process.stdout.write("checktrail <inspect|plan|run|serve|adapters|import-junit|export-sarif|create-baseline|compare-findings|compare-runtime|check-contracts|check-architecture|guidance|review-context|review-receipt|mutate|fetch-pack> [--root PATH] [--detailed] [--base REVISION] [--policy-overlay PATH] [--adapter PATH#sha256=DIGEST ...]\nRun: --trust-project [--timeout-ms 30000] [--allow-env NAME ...]\nServe: --allow-execution (optional; disabled by default) [--allow-env NAME ...]\nFetch-pack: --url HTTPS_URL --sha256 DIGEST --output RELATIVE_JSON_PATH\nExit: 0 passed/read-only success/completed advisory experiment, 1 failed checks, 2 incomplete/error\n");
66
+ process.stdout.write("checktrail <init|doctor|mcp-config|inspect|plan|run|serve|adapters|import-junit|export-sarif|create-baseline|compare-findings|compare-runtime|check-contracts|check-architecture|guidance|review-context|review-receipt|mutate|fetch-pack> [--root PATH] [--detailed] [--base REVISION] [--policy-overlay PATH] [--adapter PATH#sha256=DIGEST ...]\nInit: [--write] [--check PATH#CHECK_ID ...] (preview by default; preserves existing config)\nDoctor: [--detailed] [--policy-overlay PATH] [--allow-env NAME ...] [--adapter PATH#sha256=DIGEST ...] (no execution)\nMcp-config: --client codex|claude-code|claude-desktop|cursor|vscode (prints configuration only)\nRun: --trust-project [--timeout-ms 30000] [--allow-env NAME ...]\nServe: --allow-execution (optional; disabled by default) [--allow-env NAME ...]\nFetch-pack: --url HTTPS_URL --sha256 DIGEST --output RELATIVE_JSON_PATH\nExit: 0 passed/read-only success/completed advisory experiment, 1 failed checks, 2 incomplete/error\n");
62
67
  return;
63
68
  }
64
69
  if (positionals.length !== 1)
65
70
  throw new Error("Expected exactly one command");
66
71
  const command = positionals[0];
72
+ for (const token of tokens) {
73
+ if (token.kind !== "option")
74
+ continue;
75
+ if (["write", "check"].includes(token.name) && command !== "init")
76
+ throw new Error(`--${token.name} applies only to init`);
77
+ if (token.name === "client" && command !== "mcp-config")
78
+ throw new Error("--client applies only to mcp-config");
79
+ const allowed = command === "init"
80
+ ? ["root", "write", "check"]
81
+ : command === "doctor"
82
+ ? ["root", "detailed", "policy-overlay", "allow-env", "adapter"]
83
+ : command === "mcp-config"
84
+ ? ["root", "client"]
85
+ : undefined;
86
+ if (allowed && !allowed.includes(token.name))
87
+ throw new Error(`--${token.name} does not apply to ${command}`);
88
+ }
67
89
  if (values["allow-review-source"] &&
68
90
  (!values.detailed ||
69
91
  !["review-context", "review-receipt", "serve"].includes(command)))
@@ -85,7 +107,15 @@ async function main() {
85
107
  };
86
108
  }));
87
109
  if (externalAdapters.length &&
88
- !["inspect", "plan", "run", "serve", "adapters", "guidance"].includes(command))
110
+ ![
111
+ "inspect",
112
+ "plan",
113
+ "run",
114
+ "serve",
115
+ "adapters",
116
+ "guidance",
117
+ "doctor",
118
+ ].includes(command))
89
119
  throw new Error("External adapters apply only to inspection, planning, validation, serving and derived guidance");
90
120
  if (externalAdapters.length && command === "guidance" && values.input)
91
121
  throw new Error("External adapters require derived guidance, not an imported context");
@@ -103,7 +133,49 @@ async function main() {
103
133
  ]);
104
134
  return;
105
135
  }
136
+ if (command === "doctor") {
137
+ const result = await diagnose(values.root, {
138
+ detailed: values.detailed,
139
+ externalAdapters,
140
+ environment: inheritEnvironment(values["allow-env"] ?? []),
141
+ ...(values["policy-overlay"]
142
+ ? { policyOverlay: values["policy-overlay"] }
143
+ : {}),
144
+ });
145
+ print(result);
146
+ process.exitCode = result.status === "no-static-blockers" ? 0 : 2;
147
+ return;
148
+ }
106
149
  const root = await realpath(values.root);
150
+ if (command === "init") {
151
+ const selections = new Map();
152
+ for (const value of values.check ?? []) {
153
+ const split = value.lastIndexOf("#");
154
+ if (split <= 0 || split === value.length - 1)
155
+ throw new Error("Check selection requires PATH#CHECK_ID");
156
+ const selectedPath = value.slice(0, split);
157
+ selections.set(selectedPath, [
158
+ ...(selections.get(selectedPath) ?? []),
159
+ value.slice(split + 1),
160
+ ]);
161
+ }
162
+ const result = await initialize(root, {
163
+ write: values.write,
164
+ selections: [...selections].map(([selectedPath, checks]) => ({
165
+ path: selectedPath,
166
+ checks,
167
+ })),
168
+ });
169
+ print(result);
170
+ process.exitCode = result.status === "needs-selection" ? 2 : 0;
171
+ return;
172
+ }
173
+ if (command === "mcp-config") {
174
+ if (!mcpClients.includes(values.client))
175
+ throw new Error(`mcp-config requires --client ${mcpClients.join("|")}`);
176
+ print(await mcpConfiguration(root, values.client));
177
+ return;
178
+ }
107
179
  if (command === "fetch-pack") {
108
180
  if (!values.url || !values.sha256 || !values.output)
109
181
  throw new Error("fetch-pack requires --url HTTPS_URL --sha256 DIGEST --output RELATIVE_JSON_PATH");
@@ -3,6 +3,8 @@ export { validateContracts } from "./contracts.js";
3
3
  export type { ContractBundle, ContractReport } from "./contract-schema.js";
4
4
  export type { PlanOptions, ValidationOptions } from "./engine.js";
5
5
  export { adapters } from "./adapters.js";
6
+ export { initialize, diagnose, mcpConfiguration, mcpClients, } from "./onboarding.js";
7
+ export type { InitOptions, InitResult, DoctorIssue, DoctorResult, McpClient, } from "./onboarding.js";
6
8
  export type { PolicyPack, PackReference } from "./policy-pack.js";
7
9
  export { fetchPolicyPack } from "./fetch-pack.js";
8
10
  export type { FetchPackOptions, FetchedPack } from "./fetch-pack.js";
package/dist/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { createPlan, validate, aggregate } from "./engine.js";
2
2
  export { validateContracts } from "./contracts.js";
3
3
  export { adapters } from "./adapters.js";
4
+ export { initialize, diagnose, mcpConfiguration, mcpClients, } from "./onboarding.js";
4
5
  export { fetchPolicyPack } from "./fetch-pack.js";
5
6
  export { projectPlan, projectReport } from "./output.js";
6
7
  export { importJUnit } from "./junit.js";
@@ -0,0 +1,50 @@
1
+ import { type Config } from "./config.js";
2
+ import { type PlanOptions } from "./engine.js";
3
+ export interface InitOptions {
4
+ write?: boolean;
5
+ selections?: {
6
+ path: string;
7
+ checks: string[];
8
+ }[];
9
+ }
10
+ export interface InitResult {
11
+ schemaVersion: 1;
12
+ engineVersion: string;
13
+ status: "preview" | "created" | "preserved" | "needs-selection";
14
+ configuration: Config | null;
15
+ unresolved: {
16
+ path: string;
17
+ adapter: string;
18
+ choices: string[];
19
+ }[];
20
+ executionEnabled: false;
21
+ }
22
+ export declare function initialize(root: string, options?: InitOptions): Promise<InitResult>;
23
+ export interface DoctorIssue {
24
+ code: "configuration-error" | "empty-plan" | "unselected-project" | "unavailable-check" | "missing-executable" | "package-metadata" | "unsupported-platform";
25
+ check?: string;
26
+ adapter?: string;
27
+ project?: string;
28
+ detail?: string;
29
+ }
30
+ export interface DoctorResult {
31
+ schemaVersion: 1;
32
+ engineVersion: string;
33
+ status: "no-static-blockers" | "attention-required";
34
+ validationPerformed: false;
35
+ projects: number;
36
+ checks: number;
37
+ issues: DoctorIssue[];
38
+ unverified: string[];
39
+ }
40
+ export declare function diagnose(root: string, options?: Omit<PlanOptions, "base"> & {
41
+ detailed?: boolean;
42
+ }): Promise<DoctorResult>;
43
+ export declare const mcpClients: readonly ["codex", "claude-code", "claude-desktop", "cursor", "vscode"];
44
+ export type McpClient = (typeof mcpClients)[number];
45
+ export declare function mcpConfiguration(root: string, client: McpClient): Promise<{
46
+ client: McpClient;
47
+ format: "json" | "toml";
48
+ configuration: string;
49
+ executionEnabled: false;
50
+ }>;
@@ -0,0 +1,295 @@
1
+ import { constants } from "node:fs";
2
+ import { access, link, lstat, mkdtemp, realpath, rm, stat, writeFile, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { adapters, checksFor } from "./adapters.js";
5
+ import { configSchema } from "./config.js";
6
+ import { createPlan } from "./engine.js";
7
+ import { inventory, readProjectFile } from "./inventory.js";
8
+ import { identifyTool } from "./tool-versions.js";
9
+ import { VERSION } from "./types.js";
10
+ async function configurationExists(root) {
11
+ try {
12
+ const entry = await lstat(path.join(root, "checktrail.json"));
13
+ if (!entry.isFile() || entry.isSymbolicLink())
14
+ throw new Error("checktrail.json must be a regular file, not a symlink or directory");
15
+ return true;
16
+ }
17
+ catch (error) {
18
+ if (error.code === "ENOENT")
19
+ return false;
20
+ throw error;
21
+ }
22
+ }
23
+ export async function initialize(root, options = {}) {
24
+ root = await realpath(root);
25
+ const exists = await configurationExists(root);
26
+ const { source, plan } = await createPlan(root);
27
+ const result = {
28
+ schemaVersion: 1,
29
+ engineVersion: VERSION,
30
+ status: "preview",
31
+ configuration: null,
32
+ unresolved: [],
33
+ executionEnabled: false,
34
+ };
35
+ if (exists) {
36
+ if (options.selections?.length)
37
+ throw new Error("Existing configuration is preserved; edit its policy explicitly to change checks");
38
+ return { ...result, status: "preserved" };
39
+ }
40
+ const selected = new Map();
41
+ for (const selection of options.selections ?? []) {
42
+ if (selected.has(selection.path))
43
+ throw new Error("Duplicate selection path");
44
+ if (!selection.checks.length ||
45
+ new Set(selection.checks).size !== selection.checks.length)
46
+ throw new Error("Selections require nonempty, unique check IDs");
47
+ selected.set(selection.path, selection.checks);
48
+ }
49
+ for (const selectedPath of selected.keys())
50
+ if (!plan.projects.some((project) => project.path === selectedPath))
51
+ throw new Error("Selection does not match a discovered project path");
52
+ const grouped = new Map();
53
+ for (const project of plan.projects) {
54
+ const registered = adapters.find((adapter) => adapter.id === project.adapter).checks;
55
+ const requested = selected.get(project.path);
56
+ let ids = requested?.filter((id) => registered.includes(id));
57
+ if (!requested) {
58
+ ids = plan.checks
59
+ .filter((check) => check.project === project.path &&
60
+ check.adapter === project.adapter &&
61
+ registered.includes(check.id))
62
+ .map((check) => check.id);
63
+ if (project.adapter === "python")
64
+ ids = [];
65
+ if (project.adapter === "javascript") {
66
+ const manifest = JSON.parse(await readProjectFile(root, path.posix.join(project.path, "package.json")));
67
+ const runners = {
68
+ "node --test": "javascript.node-test",
69
+ "vitest run": "javascript.vitest",
70
+ jest: "javascript.jest",
71
+ "playwright test": "javascript.playwright",
72
+ };
73
+ const script = manifest?.scripts?.test;
74
+ ids =
75
+ typeof script === "string" && Object.hasOwn(runners, script)
76
+ ? [runners[script]]
77
+ : [];
78
+ }
79
+ }
80
+ if (!ids?.length ||
81
+ plan.checks.some((check) => check.project === project.path &&
82
+ check.adapter === project.adapter &&
83
+ check.kind === "unsupported")) {
84
+ result.unresolved.push({
85
+ path: project.path,
86
+ adapter: project.adapter,
87
+ choices: [...registered],
88
+ });
89
+ continue;
90
+ }
91
+ const candidates = await checksFor(source, project, ids);
92
+ if (ids.some((id) => !candidates.some((check) => check.id === id)))
93
+ throw new Error("Selected check is inapplicable to the discovered project");
94
+ const checks = grouped.get(project.path) ?? new Set();
95
+ for (const id of ids)
96
+ checks.add(id);
97
+ grouped.set(project.path, checks);
98
+ }
99
+ for (const [selectedPath, ids] of selected)
100
+ if (ids.some((id) => !grouped.get(selectedPath)?.has(id)))
101
+ throw new Error("Selected check is unknown or inapplicable");
102
+ if (!plan.projects.length || result.unresolved.length)
103
+ return { ...result, status: "needs-selection" };
104
+ result.configuration = configSchema.parse({
105
+ schemaVersion: 1,
106
+ projects: [...grouped].map(([projectPath, checks]) => ({
107
+ path: projectPath,
108
+ checks: [...checks].sort(),
109
+ })),
110
+ });
111
+ if (!options.write)
112
+ return result;
113
+ if ((await inventory(root)).fingerprint !== source.fingerprint)
114
+ throw new Error("Project changed during setup; review a fresh preview");
115
+ const temporary = await mkdtemp(path.join(root, ".checktrail-init-"));
116
+ try {
117
+ const staged = path.join(temporary, "checktrail.json");
118
+ await writeFile(staged, `${JSON.stringify(result.configuration, null, 2)}\n`, { flag: "wx", mode: 0o600 });
119
+ // An exclusive link publishes complete bytes without replacing a concurrent writer.
120
+ await link(staged, path.join(root, "checktrail.json"));
121
+ }
122
+ finally {
123
+ await rm(temporary, { recursive: true, force: true });
124
+ }
125
+ return { ...result, status: "created" };
126
+ }
127
+ async function executableAvailable(executable, cwd, searchPath) {
128
+ const candidates = executable.includes("/") || path.isAbsolute(executable)
129
+ ? [path.resolve(cwd, executable)]
130
+ : searchPath
131
+ .split(path.delimiter)
132
+ .map((directory) => path.resolve(cwd, directory, executable));
133
+ for (const candidate of candidates) {
134
+ try {
135
+ if (!(await stat(candidate)).isFile())
136
+ continue;
137
+ await access(candidate, constants.X_OK);
138
+ return true;
139
+ }
140
+ catch {
141
+ /* Other PATH entries may provide the executable. */
142
+ }
143
+ }
144
+ return false;
145
+ }
146
+ export async function diagnose(root, options = {}) {
147
+ const issues = [];
148
+ const result = {
149
+ schemaVersion: 1,
150
+ engineVersion: VERSION,
151
+ status: "attention-required",
152
+ validationPerformed: false,
153
+ projects: 0,
154
+ checks: 0,
155
+ issues,
156
+ unverified: [
157
+ "Tool versions and runtime compatibility",
158
+ "Importable modules and runtime services",
159
+ "Validation results; run with explicit project trust",
160
+ ],
161
+ };
162
+ const add = (issue) => {
163
+ const summary = { ...issue };
164
+ delete summary.project;
165
+ delete summary.detail;
166
+ issues.push(options.detailed ? issue : summary);
167
+ };
168
+ if (process.platform === "win32")
169
+ add({ code: "unsupported-platform" });
170
+ try {
171
+ root = await realpath(root);
172
+ await configurationExists(root);
173
+ const { plan } = await createPlan(root, {
174
+ ...(options.externalAdapters
175
+ ? { externalAdapters: options.externalAdapters }
176
+ : {}),
177
+ ...(options.policyOverlay
178
+ ? { policyOverlay: options.policyOverlay }
179
+ : {}),
180
+ ...(options.environment ? { environment: options.environment } : {}),
181
+ });
182
+ result.projects = plan.projects.length;
183
+ result.checks = plan.checks.length;
184
+ if (!plan.checks.length)
185
+ add({ code: "empty-plan" });
186
+ for (const project of plan.projects)
187
+ if (!plan.checks.some((check) => check.project === project.path && check.adapter === project.adapter))
188
+ add({
189
+ code: "unselected-project",
190
+ project: project.path,
191
+ adapter: project.adapter,
192
+ });
193
+ for (const check of plan.checks) {
194
+ const context = {
195
+ check: check.id,
196
+ adapter: check.adapter,
197
+ project: check.project,
198
+ };
199
+ if (check.unavailableReason)
200
+ add({
201
+ ...context,
202
+ code: "unavailable-check",
203
+ detail: check.unavailableReason,
204
+ });
205
+ const commands = [...check.commands];
206
+ for (const tool of check.tools ?? []) {
207
+ if (tool.source === "version-command")
208
+ commands.push(tool.command);
209
+ else if (tool.source === "package-metadata") {
210
+ const identity = await identifyTool(root, tool, async () => {
211
+ throw new Error("Doctor must not execute version probes");
212
+ });
213
+ if (identity.status !== "identified")
214
+ add({
215
+ ...context,
216
+ code: "package-metadata",
217
+ detail: `Missing or invalid package metadata: ${tool.name}`,
218
+ });
219
+ }
220
+ }
221
+ const wrapped = check.adapter === "jvm"
222
+ ? ["java"]
223
+ : check.adapter === "dotnet"
224
+ ? ["dotnet"]
225
+ : check.id === "infrastructure.actionlint"
226
+ ? ["actionlint"]
227
+ : [];
228
+ for (const executable of wrapped)
229
+ commands.push({ executable, args: [], cwd: check.project });
230
+ const checked = new Set();
231
+ for (const command of commands) {
232
+ const searchPath = command.env?.PATH ??
233
+ (check.environment?.includes("PATH")
234
+ ? options.environment?.PATH
235
+ : undefined) ??
236
+ process.env.PATH ??
237
+ "/usr/bin:/bin";
238
+ const cwd = path.resolve(root, command.cwd);
239
+ const key = JSON.stringify([command.executable, cwd, searchPath]);
240
+ if (checked.has(key))
241
+ continue;
242
+ checked.add(key);
243
+ if (!(await executableAvailable(command.executable, cwd, searchPath)))
244
+ add({
245
+ ...context,
246
+ code: "missing-executable",
247
+ detail: `Executable unavailable or not executable: ${command.executable}`,
248
+ });
249
+ }
250
+ }
251
+ }
252
+ catch (error) {
253
+ add({
254
+ code: "configuration-error",
255
+ detail: error instanceof Error ? error.message : "Cannot inspect project",
256
+ });
257
+ }
258
+ result.status = issues.length ? "attention-required" : "no-static-blockers";
259
+ return result;
260
+ }
261
+ export const mcpClients = [
262
+ "codex",
263
+ "claude-code",
264
+ "claude-desktop",
265
+ "cursor",
266
+ "vscode",
267
+ ];
268
+ export async function mcpConfiguration(root, client) {
269
+ if (!mcpClients.includes(client))
270
+ throw new Error("Unknown MCP client");
271
+ root = await realpath(root);
272
+ if (!(await stat(root)).isDirectory())
273
+ throw new Error("MCP root must be a directory");
274
+ if (root.includes("${"))
275
+ throw new Error("MCP root contains client variable syntax; choose a path without ${");
276
+ const server = {
277
+ command: "npx",
278
+ args: [
279
+ "--yes",
280
+ "--ignore-scripts",
281
+ `@stsepelin/checktrail@${VERSION}`,
282
+ "serve",
283
+ "--root",
284
+ root,
285
+ ],
286
+ };
287
+ return {
288
+ client,
289
+ executionEnabled: false,
290
+ format: client === "codex" ? "toml" : "json",
291
+ configuration: client === "codex"
292
+ ? `[mcp_servers.checktrail]\ncommand = "npx"\nargs = ${JSON.stringify(server.args)}\n`
293
+ : `${JSON.stringify(client === "vscode" ? { servers: { checktrail: { type: "stdio", ...server } } } : { mcpServers: { checktrail: server } }, null, 2)}\n`,
294
+ };
295
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ExternalIdentity } from "./external-adapter.js";
2
2
  import type { RuntimeInventory } from "./runtime-inventory.js";
3
- export declare const VERSION = "0.1.0-alpha.1";
3
+ export declare const VERSION = "0.1.0-alpha.3";
4
4
  export declare const PARSERS: readonly ["vue-router-json", "nuxt-json", "exit", "empty", "node-events", "unittest", "go-scope-test", "go-scope-analysis", "golangci-json", "staticcheck-json", "go-json", "typescript-build-json", "tsc-files", "eslint-json", "vitest-json", "playwright-json", "jest-json", "pytest-json", "fastapi-json", "django-json", "laravel-json", "rust-json", "clang-json", "java-json", "dotnet-json", "actionlint-json", "external-json", "ruby-syntax", "silent-syntax", "ruff-json", "mypy-json", "phpstan-json", "phpunit-junit", "pint-json"];
5
5
  export type Status = "passed" | "failed" | "unavailable" | "skipped" | "error" | "inconclusive";
6
6
  export type Outcome = "passed" | "failed" | "incomplete";
package/dist/src/types.js CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.1.0-alpha.1";
1
+ export const VERSION = "0.1.0-alpha.3";
2
2
  export const PARSERS = [
3
3
  "vue-router-json",
4
4
  "nuxt-json",
@@ -0,0 +1,4 @@
1
+ import type { parseCommandLine } from "typescript";
2
+ export declare function typecheckArguments(compiler: {
3
+ parseCommandLine: typeof parseCommandLine;
4
+ }, args: string[]): string[];
@@ -0,0 +1,10 @@
1
+ export function typecheckArguments(compiler, args) {
2
+ const probe = compiler.parseCommandLine(["--noCheck", "false"]);
3
+ if (probe.errors.length === 0 && probe.options.noCheck === false)
4
+ return [...args, "--noCheck", "false"];
5
+ if (probe.errors.length === 1 &&
6
+ probe.errors[0].code === 5023 &&
7
+ probe.options.noCheck === undefined)
8
+ return args;
9
+ throw new Error("Cannot establish the compiler's noCheck option support");
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { withinRoot } from "./inventory.js";
4
+ import { typecheckArguments } from "./typescript-arguments.js";
5
+ async function main() {
6
+ const [entry, root, ...args] = process.argv.slice(2);
7
+ if (!entry || !root)
8
+ throw new Error("Invalid TypeScript compiler arguments");
9
+ const tool = await withinRoot(root, path.relative(root, entry));
10
+ const api = await withinRoot(root, path.relative(root, path.resolve(path.dirname(tool), "../lib/typescript.js")));
11
+ const load = createRequire(tool);
12
+ const ts = load(api);
13
+ process.argv = [process.execPath, tool, ...typecheckArguments(ts, args)];
14
+ load(tool);
15
+ }
16
+ main().catch((error) => {
17
+ process.stderr.write(`${error instanceof Error ? error.message : "TypeScript checking failed"}\n`);
18
+ process.exitCode = 2;
19
+ });
@@ -16,18 +16,12 @@ export async function typescriptCheck(source, project, vue = false) {
16
16
  {
17
17
  executable: process.execPath,
18
18
  args: [
19
- ...(vue
20
- ? [
21
- fileURLToPath(new URL("./vue-tsc-runner.js", import.meta.url)),
22
- compiler,
23
- source.root,
24
- ]
25
- : [compiler]),
19
+ fileURLToPath(new URL(vue ? "./vue-tsc-runner.js" : "./typescript-runner.js", import.meta.url)),
20
+ compiler,
21
+ source.root,
26
22
  "--project",
27
23
  "./tsconfig.json",
28
24
  "--noEmit",
29
- "--noCheck",
30
- "false",
31
25
  "--pretty",
32
26
  "false",
33
27
  "--incremental",
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import path from "node:path";
3
+ import { typecheckArguments } from "./typescript-arguments.js";
3
4
  import { withinRoot } from "./inventory.js";
4
5
  async function main() {
5
6
  const [entry, root, ...args] = process.argv.slice(2);
@@ -15,7 +16,7 @@ async function main() {
15
16
  throw new Error("Vue template checking is disabled by skipTemplateCodegen");
16
17
  const compiler = load(tool);
17
18
  const tsc = await resolve("typescript/lib/tsc.js");
18
- process.argv = [process.execPath, tool, ...args];
19
+ process.argv = [process.execPath, tool, ...typecheckArguments(ts, args)];
19
20
  compiler.run(tsc);
20
21
  }
21
22
  main().catch((error) => {