@codapult/guard 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/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/adapters/agents/agent-integration.d.ts +9 -0
- package/dist/adapters/agents/agent-integration.js +62 -0
- package/dist/adapters/command.d.ts +21 -0
- package/dist/adapters/command.js +60 -0
- package/dist/adapters/project-checks.d.ts +37 -0
- package/dist/adapters/project-checks.js +149 -0
- package/dist/cli/commands/guard.d.ts +55 -0
- package/dist/cli/commands/guard.js +510 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +75 -0
- package/dist/cli/ui.d.ts +6 -0
- package/dist/cli/ui.js +7 -0
- package/dist/commands/guard.d.ts +2 -0
- package/dist/commands/guard.js +2 -0
- package/dist/core/analysis/doctor.d.ts +12 -0
- package/dist/core/analysis/doctor.js +85 -0
- package/dist/core/analysis/packs.d.ts +21 -0
- package/dist/core/analysis/packs.js +251 -0
- package/dist/core/config.d.ts +6 -0
- package/dist/core/config.js +6 -0
- package/dist/core/discovery/discovery.d.ts +134 -0
- package/dist/core/discovery/discovery.js +816 -0
- package/dist/core/guard.d.ts +189 -0
- package/dist/core/guard.js +936 -0
- package/dist/core/history/history.d.ts +24 -0
- package/dist/core/history/history.js +65 -0
- package/dist/core/output/sarif.d.ts +35 -0
- package/dist/core/output/sarif.js +29 -0
- package/dist/core/policy/schemas.d.ts +272 -0
- package/dist/core/policy/schemas.js +72 -0
- package/dist/core/verification/verify.d.ts +29 -0
- package/dist/core/verification/verify.js +77 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/mcp/prompts.d.ts +2 -0
- package/dist/mcp/prompts.js +54 -0
- package/dist/mcp/resources.d.ts +2 -0
- package/dist/mcp/resources.js +64 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +17 -0
- package/dist/mcp/tools/guard.d.ts +2 -0
- package/dist/mcp/tools/guard.js +375 -0
- package/package.json +117 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { GUARD_AGENT_FILE, GUARD_BASELINE_META_FILE, GUARD_ARCHITECTURE_FILE, GUARD_BASELINE_FILE, GUARD_CONVENTIONS_FILE, GUARD_CONTRACTS_FILE, GUARD_PROPOSALS_FILE, GUARD_PROJECT_FILE, GUARD_RULES_FILE, loadGuardConfig, loadProjectModel, isGuardAgentConfig, isGuardContractsFile, isGuardProposalFile, validateGuardContracts, } from '../guard.js';
|
|
4
|
+
const requiredArtifacts = [
|
|
5
|
+
GUARD_BASELINE_FILE,
|
|
6
|
+
GUARD_RULES_FILE,
|
|
7
|
+
GUARD_PROJECT_FILE,
|
|
8
|
+
GUARD_ARCHITECTURE_FILE,
|
|
9
|
+
GUARD_CONVENTIONS_FILE,
|
|
10
|
+
GUARD_AGENT_FILE,
|
|
11
|
+
GUARD_CONTRACTS_FILE,
|
|
12
|
+
GUARD_PROPOSALS_FILE,
|
|
13
|
+
];
|
|
14
|
+
function parseJson(root, path) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(resolve(root, path), 'utf8'));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function diagnoseGuard(root) {
|
|
23
|
+
const initialized = existsSync(resolve(root, GUARD_BASELINE_FILE));
|
|
24
|
+
const items = requiredArtifacts.map((path) => {
|
|
25
|
+
const guardConfig = path === GUARD_CONTRACTS_FILE ? loadGuardConfig(root) : undefined;
|
|
26
|
+
if (!existsSync(resolve(root, path))) {
|
|
27
|
+
return { path, status: 'missing', message: 'Artifact is missing.' };
|
|
28
|
+
}
|
|
29
|
+
if (path === GUARD_RULES_FILE && !loadGuardConfig(root)) {
|
|
30
|
+
return { path, status: 'invalid', message: 'Guard rules are invalid or unsupported.' };
|
|
31
|
+
}
|
|
32
|
+
if (path === GUARD_PROJECT_FILE && !loadProjectModel(root)) {
|
|
33
|
+
return { path, status: 'invalid', message: 'Project model is invalid or unsupported.' };
|
|
34
|
+
}
|
|
35
|
+
if (path === GUARD_AGENT_FILE && !isGuardAgentConfig(parseJson(root, path))) {
|
|
36
|
+
return { path, status: 'invalid', message: 'Agent policy is invalid or unsupported.' };
|
|
37
|
+
}
|
|
38
|
+
if (path === GUARD_CONTRACTS_FILE && !isGuardContractsFile(parseJson(root, path))) {
|
|
39
|
+
return { path, status: 'invalid', message: 'Guard contracts are invalid or unsupported.' };
|
|
40
|
+
}
|
|
41
|
+
if (path === GUARD_CONTRACTS_FILE &&
|
|
42
|
+
guardConfig &&
|
|
43
|
+
validateGuardContracts(root, guardConfig.contracts).length > 0) {
|
|
44
|
+
return { path, status: 'invalid', message: 'Guard contract scopes or references are stale.' };
|
|
45
|
+
}
|
|
46
|
+
if (path === GUARD_PROPOSALS_FILE && !isGuardProposalFile(parseJson(root, path))) {
|
|
47
|
+
return { path, status: 'invalid', message: 'Guard proposals are invalid or unsupported.' };
|
|
48
|
+
}
|
|
49
|
+
if (parseJson(root, path) === undefined) {
|
|
50
|
+
return { path, status: 'invalid', message: 'Artifact is invalid JSON.' };
|
|
51
|
+
}
|
|
52
|
+
return { path, status: 'ok', message: 'Artifact is present and readable.' };
|
|
53
|
+
});
|
|
54
|
+
if (initialized && existsSync(resolve(root, GUARD_BASELINE_META_FILE))) {
|
|
55
|
+
const metadata = parseJson(root, GUARD_BASELINE_META_FILE);
|
|
56
|
+
items.push({
|
|
57
|
+
path: GUARD_BASELINE_META_FILE,
|
|
58
|
+
status: metadata !== undefined &&
|
|
59
|
+
metadata !== null &&
|
|
60
|
+
typeof metadata === 'object' &&
|
|
61
|
+
metadata.version === 1
|
|
62
|
+
? 'ok'
|
|
63
|
+
: 'invalid',
|
|
64
|
+
message: 'Baseline metadata is present and readable.',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const missing = items.filter((item) => item.status === 'missing');
|
|
68
|
+
const invalid = items.filter((item) => item.status === 'invalid');
|
|
69
|
+
return {
|
|
70
|
+
status: invalid.length > 0 || (initialized && missing.length > 0)
|
|
71
|
+
? 'fail'
|
|
72
|
+
: missing.length > 0
|
|
73
|
+
? 'warning'
|
|
74
|
+
: 'ok',
|
|
75
|
+
initialized,
|
|
76
|
+
items,
|
|
77
|
+
...(invalid.length > 0
|
|
78
|
+
? {
|
|
79
|
+
recommendation: 'Run `codapult-guard init --force` after reviewing the current baseline.',
|
|
80
|
+
}
|
|
81
|
+
: missing.length > 0
|
|
82
|
+
? { recommendation: 'Run `codapult-guard analyze` or reinitialize Guard intentionally.' }
|
|
83
|
+
: {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ProjectModel } from '../discovery/discovery.js';
|
|
2
|
+
export interface GuardPackDefinition {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
capabilities: string[];
|
|
6
|
+
focus: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface GuardPackAssessment {
|
|
9
|
+
id: string;
|
|
10
|
+
status: 'observed';
|
|
11
|
+
evidence: string[];
|
|
12
|
+
gaps: string[];
|
|
13
|
+
checks: {
|
|
14
|
+
id: string;
|
|
15
|
+
status: 'observed' | 'missing';
|
|
16
|
+
evidence: string[];
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
export declare const guardPacks: GuardPackDefinition[];
|
|
20
|
+
export declare function detectGuardPacks(model: ProjectModel): GuardPackDefinition[];
|
|
21
|
+
export declare function assessGuardPacks(model: ProjectModel): GuardPackAssessment[];
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
export const guardPacks = [
|
|
2
|
+
{
|
|
3
|
+
id: 'nextjs',
|
|
4
|
+
title: 'Next.js / React boundaries',
|
|
5
|
+
capabilities: ['nextjs'],
|
|
6
|
+
focus: ['server/client boundaries', 'routes', 'Server Actions'],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: 'react',
|
|
10
|
+
title: 'React component boundaries',
|
|
11
|
+
capabilities: ['react'],
|
|
12
|
+
focus: ['component boundaries', 'client state', 'rendering contracts'],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'api',
|
|
16
|
+
title: 'API boundaries',
|
|
17
|
+
capabilities: ['routing'],
|
|
18
|
+
focus: ['authentication', 'validation', 'rate limiting', 'error contracts'],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'database',
|
|
22
|
+
title: 'Database and ORM',
|
|
23
|
+
capabilities: ['persistence'],
|
|
24
|
+
focus: ['schema drift', 'migrations', 'repository boundaries'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'auth',
|
|
28
|
+
title: 'Authentication and authorization',
|
|
29
|
+
capabilities: ['identity'],
|
|
30
|
+
focus: ['auth entrypoints', 'organization scope', 'authorization ordering'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'payments',
|
|
34
|
+
title: 'Payments and billing',
|
|
35
|
+
capabilities: ['payments', 'billing'],
|
|
36
|
+
focus: ['webhook signatures', 'idempotency', 'provider adapters'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'jobs',
|
|
40
|
+
title: 'Queues and background jobs',
|
|
41
|
+
capabilities: ['jobs', 'queues'],
|
|
42
|
+
focus: ['retries', 'idempotency', 'dead-letter handling'],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'ai',
|
|
46
|
+
title: 'AI and RAG',
|
|
47
|
+
capabilities: ['ai', 'rag'],
|
|
48
|
+
focus: ['gateway boundaries', 'organization scope', 'quotas and usage'],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'security',
|
|
52
|
+
title: 'Security',
|
|
53
|
+
capabilities: ['security'],
|
|
54
|
+
focus: ['secrets', 'SSRF', 'dependency and SAST adapters'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'deployment',
|
|
58
|
+
title: 'Deployment and operations',
|
|
59
|
+
capabilities: ['deployment', 'observability'],
|
|
60
|
+
focus: ['environment', 'health checks', 'release safety'],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'email',
|
|
64
|
+
title: 'Email and notifications',
|
|
65
|
+
capabilities: ['email'],
|
|
66
|
+
focus: ['template boundaries', 'delivery failures', 'retry safety'],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'storage',
|
|
70
|
+
title: 'File and object storage',
|
|
71
|
+
capabilities: ['storage'],
|
|
72
|
+
focus: ['upload validation', 'access control', 'public/private boundaries'],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'graphql-rpc',
|
|
76
|
+
title: 'GraphQL and RPC',
|
|
77
|
+
capabilities: ['graphql-rpc'],
|
|
78
|
+
focus: ['input validation', 'authorization', 'error contracts'],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'i18n',
|
|
82
|
+
title: 'Internationalization',
|
|
83
|
+
capabilities: ['i18n'],
|
|
84
|
+
focus: ['locale boundaries', 'message completeness', 'formatting'],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'observability',
|
|
88
|
+
title: 'Observability',
|
|
89
|
+
capabilities: ['observability'],
|
|
90
|
+
focus: ['structured logs', 'traces', 'correlation and privacy'],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'cache',
|
|
94
|
+
title: 'Caching and key-value storage',
|
|
95
|
+
capabilities: ['cache'],
|
|
96
|
+
focus: ['invalidation', 'TTL', 'tenant isolation'],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'analytics',
|
|
100
|
+
title: 'Product analytics',
|
|
101
|
+
capabilities: ['analytics'],
|
|
102
|
+
focus: ['event contracts', 'privacy', 'identity consistency'],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'search',
|
|
106
|
+
title: 'Search and indexing',
|
|
107
|
+
capabilities: ['search'],
|
|
108
|
+
focus: ['index consistency', 'query validation', 'tenant isolation'],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'content',
|
|
112
|
+
title: 'Content and CMS',
|
|
113
|
+
capabilities: ['content'],
|
|
114
|
+
focus: ['content validation', 'draft/publish flow', 'rendering safety'],
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
export function detectGuardPacks(model) {
|
|
118
|
+
const observed = new Set([
|
|
119
|
+
...Object.keys(model.capabilities),
|
|
120
|
+
...model.project.frameworks.map((framework) => framework.toLowerCase().replace(/[^a-z]/g, '')),
|
|
121
|
+
]);
|
|
122
|
+
return guardPacks.filter((pack) => pack.capabilities.some((capability) => observed.has(capability)));
|
|
123
|
+
}
|
|
124
|
+
export function assessGuardPacks(model) {
|
|
125
|
+
const packageCount = (capability) => {
|
|
126
|
+
const signal = Object.hasOwn(model.capabilities, capability)
|
|
127
|
+
? model.capabilities[capability]
|
|
128
|
+
: undefined;
|
|
129
|
+
return signal?.packages.length ?? 0;
|
|
130
|
+
};
|
|
131
|
+
return detectGuardPacks(model).map((pack) => {
|
|
132
|
+
const evidence = [];
|
|
133
|
+
const gaps = [];
|
|
134
|
+
const checks = [];
|
|
135
|
+
const files = model.files.map((file) => file.path);
|
|
136
|
+
const checkFiles = (id, pattern, message, signalPattern = pattern) => {
|
|
137
|
+
const fileMatches = files.filter((file) => pattern.test(file));
|
|
138
|
+
const moduleMatches = model.modules
|
|
139
|
+
.filter((module) => signalPattern.test([module.path, ...module.imports, ...module.importedSymbols, ...module.calls].join('\n')))
|
|
140
|
+
.map((module) => module.path);
|
|
141
|
+
const matches = [...new Set([...fileMatches, ...moduleMatches])];
|
|
142
|
+
checks.push({
|
|
143
|
+
id,
|
|
144
|
+
status: matches.length > 0 ? 'observed' : 'missing',
|
|
145
|
+
evidence: matches.slice(0, 5),
|
|
146
|
+
});
|
|
147
|
+
if (matches.length === 0)
|
|
148
|
+
gaps.push(message);
|
|
149
|
+
};
|
|
150
|
+
switch (pack.id) {
|
|
151
|
+
case 'nextjs':
|
|
152
|
+
evidence.push(`${model.patterns.routeHandlers.length} route handler(s)`);
|
|
153
|
+
checkFiles('server-client-boundary', /(?:^|\/)(?:layout|page|component|client|server)\.(?:tsx?|jsx?)$/i, 'No likely server/client boundary file was detected.');
|
|
154
|
+
if (!model.insights.boundaries.some((boundary) => boundary.kind === 'client')) {
|
|
155
|
+
gaps.push('No client/server boundary markers were observed.');
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
case 'api':
|
|
159
|
+
evidence.push(`${model.patterns.routeHandlers.length} route handler(s)`);
|
|
160
|
+
checkFiles('input-validation', /(?:schema|validation|validate|parse|zod|valibot|joi|yup)/i, 'No input-validation boundary was detected by file naming.');
|
|
161
|
+
if (packageCount('security') === 0) {
|
|
162
|
+
gaps.push('No explicit validation/security dependency was observed.');
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'database':
|
|
166
|
+
evidence.push(`${model.schemas.length} schema file(s)`);
|
|
167
|
+
checkFiles('migration-path', /(?:^|\/)(?:migrations?|drizzle|prisma)\b/i, 'No schema migration path was detected by file naming.');
|
|
168
|
+
if (model.schemas.length === 0)
|
|
169
|
+
gaps.push('No schema files were detected.');
|
|
170
|
+
break;
|
|
171
|
+
case 'auth':
|
|
172
|
+
evidence.push(`${packageCount('identity')} identity package(s)`);
|
|
173
|
+
checkFiles('authorization-boundary', /(?:auth|authentication|identity|session|permission|policy)/i, 'No authentication or authorization boundary was detected.', /auth|authentication|identity|session|permission|policy|requireUser|requireOrganization|authorize/i);
|
|
174
|
+
checkFiles('tenant-scope', /(?:tenant|organization|workspace|team|scope)/i, 'No tenant, organization, workspace, or scope signal was detected.', /tenant|organization|workspace|team|scope|orgId|tenantId/i);
|
|
175
|
+
break;
|
|
176
|
+
case 'payments':
|
|
177
|
+
evidence.push(`${packageCount('payments')} provider package(s)`);
|
|
178
|
+
checkFiles('webhook-receiver', /webhook|event/i, 'No webhook or event receiver file was detected.');
|
|
179
|
+
checkFiles('signature-verification', /(?:signature|verify|constructEvent|webhook)/i, 'No payment signature-verification signal was detected by file naming.');
|
|
180
|
+
checkFiles('idempotency', /idempot|dedup|event[-_]?log/i, 'No payment idempotency signal was detected by file naming.');
|
|
181
|
+
break;
|
|
182
|
+
case 'jobs':
|
|
183
|
+
evidence.push(`${packageCount('jobs')} queue package(s)`);
|
|
184
|
+
checkFiles('worker-boundary', /worker|queue|job|task/i, 'No queue or worker implementation file was detected.');
|
|
185
|
+
checkFiles('retry-policy', /retry|backoff|attempt/i, 'No retry/backoff signal was detected by file naming.');
|
|
186
|
+
checkFiles('idempotency', /idempot|dedup|job[-_]?key/i, 'No job idempotency signal was detected by file naming.');
|
|
187
|
+
break;
|
|
188
|
+
case 'ai':
|
|
189
|
+
evidence.push(`${packageCount('ai')} AI package(s)`);
|
|
190
|
+
checkFiles('usage-boundary', /guardrail|quota|usage|meter|limit/i, 'No usage, quota, or guardrail boundary was detected.');
|
|
191
|
+
break;
|
|
192
|
+
case 'security':
|
|
193
|
+
evidence.push(`${packageCount('security')} security package(s)`);
|
|
194
|
+
checkFiles('security-test-coverage', /(?:security|auth|permission|policy|csrf|xss).*\.(?:test|spec)\./i, 'No security-focused test file was detected by file naming.');
|
|
195
|
+
if (model.tests.files.length === 0)
|
|
196
|
+
gaps.push('No tests were detected for security-sensitive code.');
|
|
197
|
+
break;
|
|
198
|
+
case 'deployment':
|
|
199
|
+
evidence.push(`${packageCount('deployment')} deployment package(s)`);
|
|
200
|
+
checkFiles('operational-health', /health|readiness|liveness|workflow|docker/i, 'No health check, workflow, or container definition was detected.');
|
|
201
|
+
break;
|
|
202
|
+
case 'email':
|
|
203
|
+
evidence.push(`${packageCount('email')} email provider package(s)`);
|
|
204
|
+
checkFiles('template-boundary', /email|mail|template/i, 'No email template or delivery boundary was detected.');
|
|
205
|
+
checkFiles('delivery-retry', /retry|queue|job|outbox/i, 'No email retry or outbox signal was detected by file naming.');
|
|
206
|
+
break;
|
|
207
|
+
case 'storage':
|
|
208
|
+
evidence.push(`${packageCount('storage')} storage package(s)`);
|
|
209
|
+
checkFiles('upload-boundary', /upload|storage|media|asset/i, 'No upload or storage boundary was detected.');
|
|
210
|
+
checkFiles('access-control', /permission|policy|access|private|signed/i, 'No storage access-control signal was detected by file naming.');
|
|
211
|
+
break;
|
|
212
|
+
case 'graphql-rpc':
|
|
213
|
+
evidence.push(`${packageCount('graphql-rpc')} GraphQL/RPC package(s)`);
|
|
214
|
+
checkFiles('schema-boundary', /schema|resolver|procedure|rpc/i, 'No API schema or resolver boundary was detected.');
|
|
215
|
+
checkFiles('input-validation', /schema|validation|validate|parse|zod|valibot|joi|yup/i, 'No GraphQL/RPC input-validation signal was detected.');
|
|
216
|
+
break;
|
|
217
|
+
case 'i18n':
|
|
218
|
+
evidence.push(`${packageCount('i18n')} i18n package(s)`);
|
|
219
|
+
checkFiles('message-catalog', /locale|locales|messages|translation|i18n/i, 'No message catalog or locale boundary was detected.');
|
|
220
|
+
break;
|
|
221
|
+
case 'observability':
|
|
222
|
+
evidence.push(`${packageCount('observability')} observability package(s)`);
|
|
223
|
+
checkFiles('structured-logging', /log|logger|logging|telemetry|observability/i, 'No logging or telemetry boundary was detected.');
|
|
224
|
+
checkFiles('correlation', /request[-_]?id|correlation|trace|span/i, 'No request correlation or tracing signal was detected.');
|
|
225
|
+
break;
|
|
226
|
+
case 'cache':
|
|
227
|
+
evidence.push(`${packageCount('cache')} cache package(s)`);
|
|
228
|
+
checkFiles('cache-boundary', /cache|redis/i, 'No cache boundary was detected.');
|
|
229
|
+
checkFiles('invalidation-ttl', /invalidate|invalidation|ttl|expire/i, 'No cache invalidation or TTL signal was detected.');
|
|
230
|
+
break;
|
|
231
|
+
case 'analytics':
|
|
232
|
+
evidence.push(`${packageCount('analytics')} analytics package(s)`);
|
|
233
|
+
checkFiles('event-boundary', /analytics|tracking|event/i, 'No analytics event boundary was detected.');
|
|
234
|
+
checkFiles('privacy-consent', /consent|privacy|opt[-_]?out|anonymous/i, 'No analytics privacy or consent signal was detected.');
|
|
235
|
+
break;
|
|
236
|
+
case 'search':
|
|
237
|
+
evidence.push(`${packageCount('search')} search package(s)`);
|
|
238
|
+
checkFiles('index-boundary', /search|index|indexing/i, 'No search index boundary was detected.');
|
|
239
|
+
checkFiles('reindex-path', /reindex|sync|synchroni|ingest/i, 'No search reindex or synchronization signal was detected.');
|
|
240
|
+
break;
|
|
241
|
+
case 'content':
|
|
242
|
+
evidence.push(`${packageCount('content')} content package(s)`);
|
|
243
|
+
checkFiles('content-boundary', /content|cms|post|article|mdx/i, 'No content or CMS boundary was detected.');
|
|
244
|
+
checkFiles('publish-flow', /publish|draft|preview|moderation/i, 'No content draft/publish or moderation signal was detected.');
|
|
245
|
+
break;
|
|
246
|
+
default:
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
return { id: pack.id, status: 'observed', evidence, gaps, checks };
|
|
250
|
+
});
|
|
251
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export interface ProjectFileRecord {
|
|
2
|
+
path: string;
|
|
3
|
+
kind: 'source' | 'test' | 'config' | 'schema' | 'migration' | 'documentation' | 'other';
|
|
4
|
+
bytes: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ModuleRecord {
|
|
7
|
+
path: string;
|
|
8
|
+
contentHash: string;
|
|
9
|
+
imports: string[];
|
|
10
|
+
importedSymbols: string[];
|
|
11
|
+
exports: string[];
|
|
12
|
+
calls: string[];
|
|
13
|
+
resolvedImports: string[];
|
|
14
|
+
dynamicImports: string[];
|
|
15
|
+
declarations: {
|
|
16
|
+
classes: number;
|
|
17
|
+
functions: number;
|
|
18
|
+
interfaces: number;
|
|
19
|
+
types: number;
|
|
20
|
+
variables: number;
|
|
21
|
+
};
|
|
22
|
+
directives: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface CapabilitySignal {
|
|
25
|
+
status: 'observed' | 'inferred';
|
|
26
|
+
confidence: 'high' | 'medium' | 'low';
|
|
27
|
+
packages: string[];
|
|
28
|
+
files: string[];
|
|
29
|
+
imports: string[];
|
|
30
|
+
evidence: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface ProjectModel {
|
|
33
|
+
version: 1;
|
|
34
|
+
project: {
|
|
35
|
+
name?: string;
|
|
36
|
+
packageManager?: string;
|
|
37
|
+
frameworks: string[];
|
|
38
|
+
scripts: Record<string, string>;
|
|
39
|
+
dependencies: Record<string, string>;
|
|
40
|
+
devDependencies: Record<string, string>;
|
|
41
|
+
workspacePackages: {
|
|
42
|
+
path: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
private: boolean;
|
|
45
|
+
scripts: Record<string, string>;
|
|
46
|
+
dependencies: string[];
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
files: ProjectFileRecord[];
|
|
50
|
+
modules: ModuleRecord[];
|
|
51
|
+
configs: string[];
|
|
52
|
+
schemas: string[];
|
|
53
|
+
tests: {
|
|
54
|
+
files: string[];
|
|
55
|
+
scripts: Record<string, string>;
|
|
56
|
+
};
|
|
57
|
+
git: {
|
|
58
|
+
repository: boolean;
|
|
59
|
+
branch?: string;
|
|
60
|
+
dirty: boolean;
|
|
61
|
+
changedFiles: string[];
|
|
62
|
+
status: string[];
|
|
63
|
+
diffStat: string;
|
|
64
|
+
history: {
|
|
65
|
+
hash: string;
|
|
66
|
+
date: string;
|
|
67
|
+
subject: string;
|
|
68
|
+
}[];
|
|
69
|
+
};
|
|
70
|
+
patterns: {
|
|
71
|
+
clientComponents: string[];
|
|
72
|
+
serverActions: string[];
|
|
73
|
+
routeHandlers: string[];
|
|
74
|
+
routeDetails: {
|
|
75
|
+
path: string;
|
|
76
|
+
methods: string[];
|
|
77
|
+
}[];
|
|
78
|
+
barrelFiles: string[];
|
|
79
|
+
importGraphEdges: number;
|
|
80
|
+
};
|
|
81
|
+
capabilities: Record<string, CapabilitySignal>;
|
|
82
|
+
insights: {
|
|
83
|
+
layers: Record<string, string[]>;
|
|
84
|
+
cycles: string[][];
|
|
85
|
+
importHotspots: {
|
|
86
|
+
module: string;
|
|
87
|
+
count: number;
|
|
88
|
+
}[];
|
|
89
|
+
boundaries: {
|
|
90
|
+
kind: string;
|
|
91
|
+
files: string[];
|
|
92
|
+
evidence: string;
|
|
93
|
+
}[];
|
|
94
|
+
layerEdges: {
|
|
95
|
+
from: string;
|
|
96
|
+
to: string;
|
|
97
|
+
count: number;
|
|
98
|
+
}[];
|
|
99
|
+
dependencyEdges: {
|
|
100
|
+
from: string;
|
|
101
|
+
to: string;
|
|
102
|
+
}[];
|
|
103
|
+
envReferences: {
|
|
104
|
+
name: string;
|
|
105
|
+
files: string[];
|
|
106
|
+
declared: boolean;
|
|
107
|
+
}[];
|
|
108
|
+
impactPaths: {
|
|
109
|
+
entrypoint: string;
|
|
110
|
+
files: string[];
|
|
111
|
+
layers: string[];
|
|
112
|
+
boundaries: string[];
|
|
113
|
+
}[];
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export interface DiscoveryMetrics {
|
|
117
|
+
durationMs: number;
|
|
118
|
+
files: number;
|
|
119
|
+
modules: number;
|
|
120
|
+
cacheAvailable: boolean;
|
|
121
|
+
cacheHit: boolean;
|
|
122
|
+
changedFiles: number;
|
|
123
|
+
reusedModules: number;
|
|
124
|
+
}
|
|
125
|
+
export declare function findGuardRoot(from?: string): string;
|
|
126
|
+
export declare function discoverProject(root: string, options?: {
|
|
127
|
+
persistCache?: boolean;
|
|
128
|
+
}): ProjectModel;
|
|
129
|
+
export declare function discoverProjectWithMetrics(root: string, options?: {
|
|
130
|
+
persistCache?: boolean;
|
|
131
|
+
}): {
|
|
132
|
+
model: ProjectModel;
|
|
133
|
+
metrics: DiscoveryMetrics;
|
|
134
|
+
};
|