@codapult/guard 0.4.0 → 0.6.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 +11 -0
- package/dist/adapters/command.d.ts +1 -0
- package/dist/adapters/command.js +4 -0
- package/dist/adapters/project-checks.js +12 -3
- package/dist/cli/commands/guard.d.ts +1 -0
- package/dist/cli/commands/guard.js +14 -0
- package/dist/cli/index.js +2 -1
- package/dist/core/history/runs.d.ts +15 -0
- package/dist/core/history/runs.js +96 -1
- package/dist/core/model/types.d.ts +1 -1
- package/dist/mcp/resources.js +14 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -308,6 +308,14 @@ task finished
|
|
|
308
308
|
For Cursor, Claude Code, Codex, Gemini CLI, GitHub Copilot, and generic hosts, see
|
|
309
309
|
[`docs/integrations/`](docs/integrations/).
|
|
310
310
|
|
|
311
|
+
### MCP Registry
|
|
312
|
+
|
|
313
|
+
Guard is also prepared for discovery through the official [MCP Registry](https://registry.modelcontextprotocol.io/).
|
|
314
|
+
The Registry entry points to the published `@codapult/guard` npm package and its stdio MCP server;
|
|
315
|
+
it does not replace npm installation or the host-specific configuration above. Registry metadata is
|
|
316
|
+
validated during CI and published after npm in the release workflow. The Registry is currently in
|
|
317
|
+
preview, so the canonical installation path remains npm.
|
|
318
|
+
|
|
311
319
|
For workflows where the authoring agent must not approve its own policy proposals, set Guard to
|
|
312
320
|
protected mode in `rules.json`:
|
|
313
321
|
|
|
@@ -327,6 +335,9 @@ and Guard rejects the same declared actor when a proposal was generated with `GU
|
|
|
327
335
|
These variables provide declared provenance only; external branch protection or signed identity
|
|
328
336
|
remains responsible for proving who approved the change.
|
|
329
337
|
|
|
338
|
+
See [the extension map](docs/extensions.md) for the supported AI-host, CI/PR, policy-pack, tool
|
|
339
|
+
adapter, approval-governance, and observability integrations.
|
|
340
|
+
|
|
330
341
|
## CI
|
|
331
342
|
|
|
332
343
|
Copy the consumer workflow into a project that has installed and initialized Guard:
|
package/dist/adapters/command.js
CHANGED
|
@@ -24,6 +24,7 @@ function parseCommand(command) {
|
|
|
24
24
|
return { executable: windowsExecutable, args };
|
|
25
25
|
}
|
|
26
26
|
export function runProjectCommand(command, cwd, options = {}) {
|
|
27
|
+
const startedAt = Date.now();
|
|
27
28
|
const parsed = parseCommand(command);
|
|
28
29
|
if (!parsed) {
|
|
29
30
|
return {
|
|
@@ -33,6 +34,7 @@ export function runProjectCommand(command, cwd, options = {}) {
|
|
|
33
34
|
exitCode: 2,
|
|
34
35
|
stdout: '',
|
|
35
36
|
stderr: 'Unsafe or empty project command rejected.',
|
|
37
|
+
durationMs: 0,
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
try {
|
|
@@ -51,6 +53,7 @@ export function runProjectCommand(command, cwd, options = {}) {
|
|
|
51
53
|
exitCode: 0,
|
|
52
54
|
stdout: captured.value,
|
|
53
55
|
stderr: '',
|
|
56
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
54
57
|
truncated: captured.truncated,
|
|
55
58
|
};
|
|
56
59
|
}
|
|
@@ -68,6 +71,7 @@ export function runProjectCommand(command, cwd, options = {}) {
|
|
|
68
71
|
exitCode,
|
|
69
72
|
stdout: stdout.value,
|
|
70
73
|
stderr: timedOut ? `${stderr.value}\n[command timed out]` : stderr.value,
|
|
74
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
71
75
|
timedOut,
|
|
72
76
|
truncated: stdout.truncated || stderr.truncated,
|
|
73
77
|
};
|
|
@@ -12,6 +12,7 @@ function notConfigured(check) {
|
|
|
12
12
|
exitCode: 0,
|
|
13
13
|
stdout: '',
|
|
14
14
|
stderr: `${check} script is not configured`,
|
|
15
|
+
durationMs: 0,
|
|
15
16
|
};
|
|
16
17
|
}
|
|
17
18
|
function readPackageMetadata(root) {
|
|
@@ -119,6 +120,9 @@ const adapterScriptPatterns = {
|
|
|
119
120
|
'dependency-graph': /(?:dep(?:endency)?[-:]?(?:check|graph|cruise)|madge|architecture)/i,
|
|
120
121
|
security: /(?:security|semgrep|gitleaks|snyk|trivy|audit)/i,
|
|
121
122
|
'dependency-hygiene': /(?:knip|dep(?:endency)?[-:]?(?:unused|hygiene))/i,
|
|
123
|
+
sast: /(?:semgrep|codeql|bearer|sast)/i,
|
|
124
|
+
'secret-scanning': /(?:gitleaks|trufflehog|secret[-_ ]?scan)/i,
|
|
125
|
+
'dependency-audit': /(?:npm audit|pnpm audit|yarn audit|osv|snyk|trivy|dependency[-_ ]?audit)/i,
|
|
122
126
|
};
|
|
123
127
|
/** Runs only explicitly configured project scripts; Guard does not recreate these tools. */
|
|
124
128
|
export function runProjectAdapters(root, options = {}) {
|
|
@@ -126,6 +130,7 @@ export function runProjectAdapters(root, options = {}) {
|
|
|
126
130
|
const manager = packageManager(root, metadata.packageManager);
|
|
127
131
|
const scripts = metadata.scripts ?? {};
|
|
128
132
|
const results = {};
|
|
133
|
+
const executed = new Map();
|
|
129
134
|
for (const [adapter, pattern] of Object.entries(adapterScriptPatterns)) {
|
|
130
135
|
const configured = options.tooling?.[adapter];
|
|
131
136
|
const script = configured?.enabled
|
|
@@ -141,9 +146,13 @@ export function runProjectAdapters(root, options = {}) {
|
|
|
141
146
|
continue;
|
|
142
147
|
}
|
|
143
148
|
const command = manager === 'npm' ? `npm run ${script}` : `${manager} run ${script}`;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
const previous = executed.get(command);
|
|
150
|
+
const result = previous ??
|
|
151
|
+
runProjectCommand(command, root, {
|
|
152
|
+
timeout: options.timeout ?? 120_000,
|
|
153
|
+
});
|
|
154
|
+
executed.set(command, result);
|
|
155
|
+
results[adapter] = result;
|
|
147
156
|
}
|
|
148
157
|
return results;
|
|
149
158
|
}
|
|
@@ -42,6 +42,7 @@ export declare function guardDoctorCommand(options?: GuardOutputOptions & {
|
|
|
42
42
|
fixCache?: boolean | undefined;
|
|
43
43
|
}): void;
|
|
44
44
|
export declare function guardHistoryCommand(): void;
|
|
45
|
+
export declare function guardRunsCommand(options?: GuardOutputOptions): void;
|
|
45
46
|
export declare function guardImpactCommand(files: string[], options?: GuardOutputOptions): void;
|
|
46
47
|
export declare function guardHistoryDiffCommand(from: string, to: string, options?: GuardOutputOptions): void;
|
|
47
48
|
export declare function guardVerifyCommand(options?: GuardVerifyOptions): void;
|
|
@@ -9,6 +9,7 @@ import { guardAgentTargets, installGuardAgentInstructions, installGuardAgentTarg
|
|
|
9
9
|
import { dim, fail, heading, info, success, warn } from '../ui.js';
|
|
10
10
|
import { guardFindingsToSarif } from '../../core/output/sarif.js';
|
|
11
11
|
import { analyzeProjectImpact } from '../../core/analysis/impact.js';
|
|
12
|
+
import { listGuardRuns, summarizeGuardRuns } from '../../core/history/runs.js';
|
|
12
13
|
function renderFindings(findings) {
|
|
13
14
|
for (const finding of findings) {
|
|
14
15
|
const printer = finding.severity === 'error' ? fail : finding.severity === 'warning' ? warn : info;
|
|
@@ -194,6 +195,19 @@ export function guardHistoryCommand() {
|
|
|
194
195
|
info(`${snapshot.revision}: ${snapshot.files} files, ${snapshot.modules} modules, ${snapshot.cycles} cycles`);
|
|
195
196
|
}
|
|
196
197
|
}
|
|
198
|
+
export function guardRunsCommand(options = {}) {
|
|
199
|
+
const root = getRoot();
|
|
200
|
+
const result = { summary: summarizeGuardRuns(root), runs: listGuardRuns(root) };
|
|
201
|
+
if (options.json) {
|
|
202
|
+
console.log(JSON.stringify(result, null, 2));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
heading('Codapult Guard Runs');
|
|
206
|
+
info(`${result.summary.total} run(s); ${result.summary.failed} failed, ${result.summary.warnings} warning, average ${result.summary.averageDurationMs} ms`);
|
|
207
|
+
for (const run of result.runs) {
|
|
208
|
+
info(`${run.runId}: ${run.outcome}, gate=${run.gate}, ${run.durationMs} ms`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
197
211
|
export function guardImpactCommand(files, options = {}) {
|
|
198
212
|
const root = getRoot();
|
|
199
213
|
if (files.length === 0) {
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import { guardAnalyzeCommand, guardAuditCommand, guardBaselineCommand, guardCheckCommand, guardContractsApproveCommand, guardContractsRejectCommand, guardDoctorCommand, guardHistoryCommand, guardHistoryDiffCommand, guardImpactCommand, guardInitCommand, guardInstallAgentCommand, guardPolicyExplainCommand, guardProposeCommand, guardReviewCommand, guardRulesApproveCommand, guardVerifyCommand, } from './commands/guard.js';
|
|
4
|
+
import { guardAnalyzeCommand, guardAuditCommand, guardBaselineCommand, guardCheckCommand, guardContractsApproveCommand, guardContractsRejectCommand, guardDoctorCommand, guardHistoryCommand, guardHistoryDiffCommand, guardImpactCommand, guardInitCommand, guardInstallAgentCommand, guardPolicyExplainCommand, guardProposeCommand, guardReviewCommand, guardRunsCommand, guardRulesApproveCommand, guardVerifyCommand, } from './commands/guard.js';
|
|
5
5
|
import { config } from '../core/config.js';
|
|
6
6
|
import { GuardBaselineReasonError, GuardConfigError, GuardStateBusyError, GuardStateStaleError, } from '../core/guard.js';
|
|
7
7
|
import { guardErrorPayload } from '../core/errors.js';
|
|
@@ -22,6 +22,7 @@ guard.command('propose').option('--json').action(guardProposeCommand);
|
|
|
22
22
|
guard.command('install-agent [target]').option('--json').action(guardInstallAgentCommand);
|
|
23
23
|
guard.command('doctor').option('--json').option('--fix-cache').action(guardDoctorCommand);
|
|
24
24
|
guard.command('history').action(guardHistoryCommand);
|
|
25
|
+
guard.command('runs').option('--json').action(guardRunsCommand);
|
|
25
26
|
guard.command('history-diff <from> <to>').option('--json').action(guardHistoryDiffCommand);
|
|
26
27
|
guard.command('impact <files...>').option('--json').action(guardImpactCommand);
|
|
27
28
|
guard
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const GUARD_RUNS_DIR: string;
|
|
2
|
+
export declare const GUARD_RUN_RETENTION = 200;
|
|
2
3
|
export interface GuardRunStage {
|
|
3
4
|
durationMs: number;
|
|
4
5
|
status: 'ok' | 'fail' | 'skipped';
|
|
@@ -22,7 +23,21 @@ export interface GuardRunContext {
|
|
|
22
23
|
startedAtMs: number;
|
|
23
24
|
stages: Record<string, GuardRunStage>;
|
|
24
25
|
}
|
|
26
|
+
export interface GuardRunSummary {
|
|
27
|
+
total: number;
|
|
28
|
+
passed: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
warnings: number;
|
|
31
|
+
averageDurationMs: number;
|
|
32
|
+
last?: GuardRunManifest | undefined;
|
|
33
|
+
}
|
|
34
|
+
export declare class GuardRunManifestError extends Error {
|
|
35
|
+
constructor();
|
|
36
|
+
}
|
|
25
37
|
export declare function startGuardRun(): GuardRunContext;
|
|
26
38
|
export declare function recordGuardRunStage(context: GuardRunContext, name: string, status: GuardRunStage['status'], startedAtMs: number, detail?: string): void;
|
|
27
39
|
export declare function writeGuardRun(root: string, manifest: GuardRunManifest): void;
|
|
40
|
+
/** Reads locally persisted run manifests without reaching a remote service. */
|
|
41
|
+
export declare function listGuardRuns(root: string, limit?: number): GuardRunManifest[];
|
|
42
|
+
export declare function summarizeGuardRuns(root: string, limit?: number): GuardRunSummary;
|
|
28
43
|
export declare function finishGuardRun(root: string, context: GuardRunContext, outcome: GuardRunManifest['outcome'], gate: string, command?: string): GuardRunManifest;
|
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
|
-
import { mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
import { config } from '../config.js';
|
|
6
6
|
export const GUARD_RUNS_DIR = `.${config.appName}/guard/history/runs`;
|
|
7
|
+
export const GUARD_RUN_RETENTION = 200;
|
|
8
|
+
export class GuardRunManifestError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('Guard run manifest is invalid or contains an unsafe run ID.');
|
|
11
|
+
this.name = 'GuardRunManifestError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function isGuardRunManifest(value) {
|
|
15
|
+
if (value === null || typeof value !== 'object')
|
|
16
|
+
return false;
|
|
17
|
+
const manifest = value;
|
|
18
|
+
return (manifest.version === 1 &&
|
|
19
|
+
typeof manifest.runId === 'string' &&
|
|
20
|
+
/^guard-[A-Za-z0-9-]+$/.test(manifest.runId) &&
|
|
21
|
+
typeof manifest.command === 'string' &&
|
|
22
|
+
typeof manifest.startedAt === 'string' &&
|
|
23
|
+
Number.isFinite(Date.parse(manifest.startedAt)) &&
|
|
24
|
+
typeof manifest.completedAt === 'string' &&
|
|
25
|
+
Number.isFinite(Date.parse(manifest.completedAt)) &&
|
|
26
|
+
typeof manifest.durationMs === 'number' &&
|
|
27
|
+
Number.isFinite(manifest.durationMs) &&
|
|
28
|
+
manifest.durationMs >= 0 &&
|
|
29
|
+
(manifest.outcome === 'pass' ||
|
|
30
|
+
manifest.outcome === 'fail' ||
|
|
31
|
+
manifest.outcome === 'warning' ||
|
|
32
|
+
manifest.outcome === 'needs-review' ||
|
|
33
|
+
manifest.outcome === 'not-configured') &&
|
|
34
|
+
typeof manifest.gate === 'string' &&
|
|
35
|
+
manifest.stages !== null &&
|
|
36
|
+
typeof manifest.stages === 'object');
|
|
37
|
+
}
|
|
7
38
|
export function startGuardRun() {
|
|
8
39
|
return {
|
|
9
40
|
runId: `guard-${randomUUID()}`,
|
|
@@ -19,13 +50,77 @@ export function recordGuardRunStage(context, name, status, startedAtMs, detail)
|
|
|
19
50
|
...(detail ? { detail } : {}),
|
|
20
51
|
};
|
|
21
52
|
}
|
|
53
|
+
function pruneGuardRuns(root) {
|
|
54
|
+
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
55
|
+
try {
|
|
56
|
+
const files = readdirSync(directory)
|
|
57
|
+
.filter((file) => file.endsWith('.json'))
|
|
58
|
+
.map((file) => ({
|
|
59
|
+
file,
|
|
60
|
+
modifiedAt: statSync(resolve(directory, file)).mtimeMs,
|
|
61
|
+
}))
|
|
62
|
+
.sort((left, right) => right.modifiedAt - left.modifiedAt);
|
|
63
|
+
for (const entry of files.slice(GUARD_RUN_RETENTION)) {
|
|
64
|
+
try {
|
|
65
|
+
unlinkSync(resolve(directory, entry.file));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Retention is best effort; a concurrent reader may own the file.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Diagnostics must never fail because retention cannot be completed.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
22
76
|
export function writeGuardRun(root, manifest) {
|
|
77
|
+
if (!isGuardRunManifest(manifest))
|
|
78
|
+
throw new GuardRunManifestError();
|
|
23
79
|
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
24
80
|
mkdirSync(directory, { recursive: true });
|
|
25
81
|
const path = resolve(directory, `${manifest.runId}.json`);
|
|
26
82
|
const temporaryPath = `${path}.tmp-${process.pid}-${manifest.runId}`;
|
|
27
83
|
writeFileSync(temporaryPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
28
84
|
renameSync(temporaryPath, path);
|
|
85
|
+
pruneGuardRuns(root);
|
|
86
|
+
}
|
|
87
|
+
/** Reads locally persisted run manifests without reaching a remote service. */
|
|
88
|
+
export function listGuardRuns(root, limit = 20) {
|
|
89
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
90
|
+
return [];
|
|
91
|
+
const boundedLimit = Math.min(limit, 1_000);
|
|
92
|
+
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
93
|
+
try {
|
|
94
|
+
return readdirSync(directory)
|
|
95
|
+
.filter((file) => file.endsWith('.json'))
|
|
96
|
+
.map((file) => {
|
|
97
|
+
try {
|
|
98
|
+
const value = JSON.parse(readFileSync(resolve(directory, file), 'utf8'));
|
|
99
|
+
return isGuardRunManifest(value) ? value : undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.filter((manifest) => manifest !== undefined)
|
|
106
|
+
.sort((left, right) => right.completedAt.localeCompare(left.completedAt))
|
|
107
|
+
.slice(0, boundedLimit);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function summarizeGuardRuns(root, limit = 100) {
|
|
114
|
+
const runs = listGuardRuns(root, limit);
|
|
115
|
+
const total = runs.length;
|
|
116
|
+
return {
|
|
117
|
+
total,
|
|
118
|
+
passed: runs.filter((run) => run.outcome === 'pass').length,
|
|
119
|
+
failed: runs.filter((run) => run.outcome === 'fail').length,
|
|
120
|
+
warnings: runs.filter((run) => run.outcome === 'warning').length,
|
|
121
|
+
averageDurationMs: total === 0 ? 0 : Math.round(runs.reduce((sum, run) => sum + run.durationMs, 0) / total),
|
|
122
|
+
...(total > 0 ? { last: runs[0] } : {}),
|
|
123
|
+
};
|
|
29
124
|
}
|
|
30
125
|
export function finishGuardRun(root, context, outcome, gate, command = 'verify') {
|
|
31
126
|
let commit;
|
|
@@ -6,5 +6,5 @@ export type GuardContractKind = 'guidance' | 'import-boundary' | 'required-call'
|
|
|
6
6
|
export type GuardBudgetMetric = 'lines' | 'bytes' | 'imports';
|
|
7
7
|
export type GuardToolMode = 'auto' | 'on' | 'off';
|
|
8
8
|
export type GuardApprovalMode = 'local' | 'protected';
|
|
9
|
-
export type GuardAdapterName = 'dependency-graph' | 'security' | 'dependency-hygiene';
|
|
9
|
+
export type GuardAdapterName = 'dependency-graph' | 'security' | 'dependency-hygiene' | 'sast' | 'secret-scanning' | 'dependency-audit';
|
|
10
10
|
export type ProjectCheck = 'lint' | 'typecheck' | 'test' | 'build';
|
package/dist/mcp/resources.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildArchitectureMemory, buildConventionsMemory, discoverProject, findGuardRoot, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, loadGuardAgentConfig, loadGuardArtifact, loadGuardConfig, loadGuardProposals, GuardStateBusyError, GuardStateStaleError, } from '../core/guard.js';
|
|
2
2
|
import { guardErrorPayload } from '../core/errors.js';
|
|
3
3
|
import { DiscoveryStaleError } from '../core/discovery/discovery.js';
|
|
4
|
+
import { listGuardRuns, summarizeGuardRuns } from '../core/history/runs.js';
|
|
4
5
|
function jsonResource(uri, value) {
|
|
5
6
|
return {
|
|
6
7
|
contents: [{ uri, text: JSON.stringify(value, null, 2), mimeType: 'application/json' }],
|
|
@@ -20,6 +21,19 @@ function resourceError(uri, error) {
|
|
|
20
21
|
}));
|
|
21
22
|
}
|
|
22
23
|
export function registerGuardResources(server) {
|
|
24
|
+
server.registerResource('codapult_guard_runs', 'codapult://guard/runs', {
|
|
25
|
+
title: 'Guard Run History',
|
|
26
|
+
description: 'Local verification outcomes, gates, and stage timings.',
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
}, () => {
|
|
29
|
+
const root = findGuardRoot();
|
|
30
|
+
return jsonResource('codapult://guard/runs', {
|
|
31
|
+
version: 1,
|
|
32
|
+
root,
|
|
33
|
+
summary: summarizeGuardRuns(root),
|
|
34
|
+
runs: listGuardRuns(root),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
23
37
|
server.registerResource('codapult_guard_rules', 'codapult://guard/rules', {
|
|
24
38
|
title: 'Architecture Guard Rules',
|
|
25
39
|
description: 'Local architecture invariants available to CLI checks and AI agents',
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codapult/guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"mcpName": "io.github.codapult/guard",
|
|
4
5
|
"description": "Local-first architecture guardrails for JavaScript and TypeScript projects, with first-class support for Next.js SaaS and AI-assisted development",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"packageManager": "pnpm@12.4.2+sha512.08adc6613180275c7c9edada39dcf08c9c61ad4e7eaf330a4f3461f102b0f907423454d117f98e72d47fef0616070644d7bffc973a6a57f5090a6d7c368b07c9",
|
|
@@ -53,7 +54,9 @@
|
|
|
53
54
|
"lint:fix": "eslint --fix .",
|
|
54
55
|
"format": "prettier --write .",
|
|
55
56
|
"format:check": "prettier --check .",
|
|
56
|
-
"release:check": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build && npm pack --dry-run",
|
|
57
|
+
"release:check": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build && npm pack --dry-run && pnpm mcp:validate",
|
|
58
|
+
"mcp:validate": "node scripts/mcp-registry.mjs validate",
|
|
59
|
+
"mcp:sync": "node scripts/mcp-registry.mjs sync",
|
|
57
60
|
"test": "vitest run",
|
|
58
61
|
"test:coverage": "vitest run --coverage",
|
|
59
62
|
"test:guard:coverage": "vitest run src/core src/adapters/project-checks.test.ts --coverage --coverage.include='src/core/**/*.ts' --coverage.include='src/adapters/project-checks.ts'",
|