@komatikai/trailhead 4.4.4 → 4.5.1

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.
Files changed (46) hide show
  1. package/README.md +43 -39
  2. package/dist/index.js +12147 -271
  3. package/dist/index.js.map +1 -1
  4. package/dist/licenses.txt +51 -0
  5. package/dist/sourcemap-register.js +1 -0
  6. package/package.json +8 -7
  7. package/dist/index.d.ts +0 -2
  8. package/dist/run-doctor.d.ts +0 -1
  9. package/dist/run-doctor.js +0 -83
  10. package/dist/run-doctor.js.map +0 -1
  11. package/dist/shared/ci-core.d.ts +0 -18
  12. package/dist/shared/ci-core.js +0 -210
  13. package/dist/shared/ci-core.js.map +0 -1
  14. package/dist/shared/ci-manifest.d.ts +0 -82
  15. package/dist/shared/ci-manifest.js +0 -51
  16. package/dist/shared/ci-manifest.js.map +0 -1
  17. package/dist/shared/config-core.d.ts +0 -5
  18. package/dist/shared/config-core.js +0 -127
  19. package/dist/shared/config-core.js.map +0 -1
  20. package/dist/shared/doctor.d.ts +0 -48
  21. package/dist/shared/doctor.js +0 -308
  22. package/dist/shared/doctor.js.map +0 -1
  23. package/dist/shared/release-ready.d.ts +0 -27
  24. package/dist/shared/release-ready.js +0 -97
  25. package/dist/shared/release-ready.js.map +0 -1
  26. package/dist/shared/submission-checks/detectors.d.ts +0 -23
  27. package/dist/shared/submission-checks/detectors.js +0 -547
  28. package/dist/shared/submission-checks/detectors.js.map +0 -1
  29. package/dist/shared/submission-checks/helpers.d.ts +0 -30
  30. package/dist/shared/submission-checks/helpers.js +0 -119
  31. package/dist/shared/submission-checks/helpers.js.map +0 -1
  32. package/dist/shared/submission-checks/phase0-detectors.d.ts +0 -18
  33. package/dist/shared/submission-checks/phase0-detectors.js +0 -374
  34. package/dist/shared/submission-checks/phase0-detectors.js.map +0 -1
  35. package/dist/shared/submission-checks/syntax-validity.d.ts +0 -2
  36. package/dist/shared/submission-checks/syntax-validity.js +0 -44
  37. package/dist/shared/submission-checks/syntax-validity.js.map +0 -1
  38. package/dist/shared/submission-checks/types.d.ts +0 -33
  39. package/dist/shared/submission-checks/types.js +0 -2
  40. package/dist/shared/submission-checks/types.js.map +0 -1
  41. package/dist/shared/submission-engine.d.ts +0 -19
  42. package/dist/shared/submission-engine.js +0 -51
  43. package/dist/shared/submission-engine.js.map +0 -1
  44. package/dist/shared/types.d.ts +0 -2644
  45. package/dist/shared/types.js +0 -487
  46. package/dist/shared/types.js.map +0 -1
@@ -1,97 +0,0 @@
1
- /**
2
- * Composite release readiness decision (ADR-006).
3
- */
4
- export function computeReleaseReady(input) {
5
- const reasons = [];
6
- if (input.gateMode === "risk-only") {
7
- if (input.gateDecision === "block") {
8
- reasons.push("Risk/policy gate decision is BLOCK");
9
- }
10
- else if (input.gateDecision === "warn") {
11
- reasons.push("Risk/policy gate decision is WARN (non-blocking in risk-only mode)");
12
- }
13
- return {
14
- releaseReady: input.gateDecision !== "block",
15
- reasons,
16
- };
17
- }
18
- if (input.ciSummary) {
19
- if (!input.ciSummary.allRequiredPassed) {
20
- const failed = input.ciSummary.checks.filter((c) => c.required &&
21
- (c.status === "fail" || c.status === "missing" || c.status === "stale"));
22
- for (const check of failed) {
23
- reasons.push(`Required CI check "${check.name}" is ${check.status.toUpperCase()}`);
24
- }
25
- }
26
- if (input.ciSummary.pendingCount > 0) {
27
- reasons.push(`${input.ciSummary.pendingCount} required CI check(s) still pending`);
28
- }
29
- }
30
- if (input.gateDecision === "block") {
31
- reasons.push("Risk/policy gate decision is BLOCK");
32
- }
33
- if (input.riskScore > input.riskThreshold) {
34
- reasons.push(`Risk score ${input.riskScore} exceeds threshold ${input.riskThreshold}`);
35
- }
36
- if (input.freezeActive) {
37
- reasons.push(`Release freeze active${input.freezeMessage ? `: ${input.freezeMessage}` : ""}`);
38
- }
39
- if (input.healthChecksConfigured && input.healthScore < 50) {
40
- reasons.push(`Health score ${input.healthScore} below minimum (50)`);
41
- }
42
- if (input.requireSecurityClear && input.securityBlocked) {
43
- reasons.push("Security gate requires clearance — blocking alerts present");
44
- }
45
- const blockingFindings = (input.policyFindings ?? []).filter((f) => /blocking|requires|exceeds|configured to block/i.test(f));
46
- if (blockingFindings.length > 0 && input.gateDecision === "block") {
47
- for (const finding of blockingFindings.slice(0, 3)) {
48
- if (!reasons.includes(finding))
49
- reasons.push(finding);
50
- }
51
- }
52
- const releaseReady = reasons.length === 0;
53
- return { releaseReady, reasons };
54
- }
55
- export function applyReleaseReadyToEvaluation(evaluation, result, gateMode) {
56
- return {
57
- ...evaluation,
58
- releaseReady: result.releaseReady,
59
- releaseReadyReasons: result.reasons.length > 0 ? result.reasons : undefined,
60
- gateMode,
61
- };
62
- }
63
- export function checkConclusionForEvaluation(evaluation) {
64
- const mode = evaluation.gateMode ?? "risk-only";
65
- if (mode === "advisory") {
66
- return "neutral";
67
- }
68
- if (mode === "release-ready") {
69
- return evaluation.releaseReady ? "success" : "failure";
70
- }
71
- switch (evaluation.gateDecision) {
72
- case "allow":
73
- return "success";
74
- case "warn":
75
- return "neutral";
76
- case "block":
77
- return "failure";
78
- default: {
79
- const _exhaustive = evaluation.gateDecision;
80
- return "failure";
81
- }
82
- }
83
- }
84
- export function shouldBlockMerge(evaluation) {
85
- const mode = evaluation.gateMode ?? "risk-only";
86
- if (mode === "advisory")
87
- return false;
88
- if (mode === "release-ready")
89
- return evaluation.releaseReady === false;
90
- return evaluation.gateDecision === "block";
91
- }
92
- export function resolveCheckName(gateMode, configuredName) {
93
- if (gateMode === "risk-only")
94
- return "Trailhead";
95
- return configuredName ?? "Trailhead — Release Ready";
96
- }
97
- //# sourceMappingURL=release-ready.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"release-ready.js","sourceRoot":"","sources":["../../src/shared/release-ready.ts"],"names":[],"mappings":"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"}
@@ -1,23 +0,0 @@
1
- import type { SubmissionCheckResult } from "../types.js";
2
- import type { SubmissionCheckContext } from "./types.js";
3
- export declare const OLD_NAME_PATTERNS: Array<{
4
- oldName: string;
5
- newName: string;
6
- pattern: RegExp;
7
- }>;
8
- export declare function detectMockPlaceholder(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
9
- export declare function detectSecrets(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
10
- export declare function detectDestructiveSql(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
11
- export declare function detectArtifactIntegrity(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
12
- export declare function detectContextFreshness(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
13
- export declare function detectPathFormat(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
14
- export declare function detectHardcodedEnv(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
15
- export declare function detectLargeFile(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
16
- export declare function detectImportResolution(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
17
- export declare function detectRlsNewTables(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
18
- export declare function detectAuthRouteAuth(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
19
- export declare function detectExternalPackageDeps(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
20
- export declare function detectSqlSyntaxBasic(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
21
- export declare function detectSyntaxValidity(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
22
- export declare function detectSoulIntegrity(ctx: SubmissionCheckContext): SubmissionCheckResult | null;
23
- export declare function runAllDetectors(ctx: SubmissionCheckContext): SubmissionCheckResult[];
@@ -1,547 +0,0 @@
1
- // Gate 1 detectors — ported from komatik-agents agent-gate-checks (patch/content based).
2
- import { addedLines, extensionOf, extractAllImports, fileContent, isStaleArchivedPath, isTestPath, lineCountFromPatch, linesForFreshnessScan, normalizePath, scanAddedContent, } from "./helpers.js";
3
- import { runPhase0Detectors } from "./phase0-detectors.js";
4
- import { validateFileSyntax } from "./syntax-validity.js";
5
- export const OLD_NAME_PATTERNS = [
6
- { oldName: "DeployGuard", newName: "Trailhead", pattern: /\bDeployGuard\b/g },
7
- { oldName: "Daydream Studio", newName: "Sundog", pattern: /\bDaydream Studio\b/g },
8
- {
9
- oldName: "Storyboard Studio",
10
- newName: "Kindling",
11
- pattern: /\bStoryboard Studio\b/g,
12
- },
13
- { oldName: "Cognitive Debt", newName: "Drift", pattern: /\bCognitive Debt\b/g },
14
- { oldName: "cognitive-debt", newName: "Drift", pattern: /\bcognitive-debt\b/g },
15
- ];
16
- const SLUG_ONLY_PATTERNS = [
17
- /\bcognitive-debt\b/,
18
- /\bstoryboard-studio\b/,
19
- /\bdaydream-studio\b/,
20
- /\bshadow-ai-governance\b/,
21
- ];
22
- const MOCK_PATTERNS = [
23
- /\bTODO\s*\(\s*mock\s*\)/i,
24
- /\bFIXME\s*\(\s*mock\s*\)/i,
25
- /\bMOCK_[A-Z0-9_]+\b/,
26
- /\bfakeImplementation\b/,
27
- /\bstubResponse\s*\(/i,
28
- /\b(?:generate|create|build|get)(?:Mock|Fake|Dummy|Sample)\w*/g,
29
- /\b(?:mockData|fakeData|sampleData|dummyData|testData)\b/g,
30
- /\bTODO:\s*implement\b/gi,
31
- /\bFIXME\b/g,
32
- /\bIn production,\s*use\b/i,
33
- /\bplaceholder\b/gi,
34
- /\blorem ipsum\b/gi,
35
- ];
36
- const SECRET_PATTERNS = [
37
- { name: "AWS access key", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
38
- { name: "GitHub token", pattern: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g },
39
- { name: "Stripe live key", pattern: /\bsk_live_[A-Za-z0-9]{10,}\b/g },
40
- { name: "Stripe test key", pattern: /\bsk_test_[A-Za-z0-9]{10,}\b/g },
41
- { name: "Private key block", pattern: /-----BEGIN [A-Z ]+PRIVATE KEY-----/g },
42
- {
43
- name: "Generic API key assignment",
44
- pattern: /api[_-]?key\s*[:=]\s*['"][A-Za-z0-9_-]{32,}['"]/gi,
45
- },
46
- ];
47
- const HARDCODED_ENV_PATTERNS = [
48
- { name: "localhost with port", pattern: /(?:['"`])localhost:\d{2,5}(?:['"`])/g },
49
- {
50
- name: "hardcoded private IP",
51
- pattern: /(?:['"`])(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})(?::\d+)?(?:['"`])/g,
52
- },
53
- ];
54
- const NODE_BUILTINS = new Set([
55
- "assert",
56
- "async_hooks",
57
- "buffer",
58
- "child_process",
59
- "cluster",
60
- "console",
61
- "constants",
62
- "crypto",
63
- "dgram",
64
- "dns",
65
- "events",
66
- "fs",
67
- "http",
68
- "http2",
69
- "https",
70
- "module",
71
- "net",
72
- "os",
73
- "path",
74
- "process",
75
- "stream",
76
- "url",
77
- "util",
78
- "zlib",
79
- ]);
80
- function result(partial) {
81
- return { autofix_eligible: false, ...partial };
82
- }
83
- export function detectMockPlaceholder(ctx) {
84
- const hits = scanAddedContent(ctx.files, (line, filename) => {
85
- if (isTestPath(filename))
86
- return false;
87
- if (/\.(md|txt)$/i.test(filename))
88
- return false;
89
- return MOCK_PATTERNS.some((re) => {
90
- re.lastIndex = 0;
91
- return re.test(line);
92
- });
93
- });
94
- if (hits.length === 0)
95
- return null;
96
- return result({
97
- code: "mock_placeholder",
98
- severity: "blocking",
99
- title: "Mock placeholder in production path",
100
- detail: `Found mock/TODO placeholder patterns in ${hits.join(", ")}.`,
101
- files: hits,
102
- suggested_action: "Remove mock placeholders and implement real behavior.",
103
- });
104
- }
105
- export function detectSecrets(ctx) {
106
- const hits = scanAddedContent(ctx.files, (line) => SECRET_PATTERNS.some((entry) => {
107
- entry.pattern.lastIndex = 0;
108
- return entry.pattern.test(line);
109
- }));
110
- if (hits.length === 0)
111
- return null;
112
- return result({
113
- code: "secrets",
114
- severity: "blocking",
115
- title: "Potential secret in diff",
116
- detail: `Added lines match secret patterns in ${hits.join(", ")}.`,
117
- files: hits,
118
- suggested_action: "Remove secrets; use environment variables or a secret manager.",
119
- });
120
- }
121
- export function detectDestructiveSql(ctx) {
122
- const sqlFiles = ctx.files.filter((f) => extensionOf(f.filename) === ".sql");
123
- const hits = scanAddedContent(sqlFiles, (line) => /\b(DROP\s+TABLE|TRUNCATE|DELETE\s+FROM(?![^\n]*\bWHERE\b))/i.test(line));
124
- if (hits.length === 0)
125
- return null;
126
- return result({
127
- code: "destructive_sql",
128
- severity: "blocking",
129
- title: "Destructive SQL in migration",
130
- detail: `Added SQL contains destructive statements in ${hits.join(", ")}.`,
131
- files: hits,
132
- suggested_action: "Use additive migrations; avoid DROP/TRUNCATE without human approval.",
133
- });
134
- }
135
- export function detectArtifactIntegrity(ctx) {
136
- const referenced = new Set();
137
- const pathRefPattern = /(?:^|\s|['"`])([\w@./-]+\.(?:ts|tsx|js|jsx|md|sql|yml|yaml|json))(?:['"`]|\s|:)/g;
138
- for (const file of ctx.files) {
139
- for (const line of addedLines(file.patch)) {
140
- if (!/(?:import|from|require|see|fix|update)\s/i.test(line))
141
- continue;
142
- for (const match of line.matchAll(pathRefPattern)) {
143
- const candidate = match[1]?.replace(/^\.\//, "");
144
- if (!candidate || candidate.includes("*"))
145
- continue;
146
- if (!ctx.prPaths.has(candidate) && !candidate.startsWith("node:")) {
147
- referenced.add(candidate);
148
- }
149
- }
150
- }
151
- }
152
- if (referenced.size === 0)
153
- return null;
154
- const missing = [...referenced].slice(0, 8);
155
- return result({
156
- code: "artifact_integrity",
157
- severity: "blocking",
158
- title: "Referenced files missing from PR",
159
- detail: `Added lines reference paths not in this PR: ${missing.join(", ")}${referenced.size > 8 ? "…" : ""}.`,
160
- files: missing,
161
- suggested_action: "Include referenced files or fix hallucinated paths.",
162
- });
163
- }
164
- function isNamingAllowlisted(filename, line, allowlist = {}) {
165
- const trimmed = line.trim();
166
- const path = normalizePath(filename);
167
- const ext = extensionOf(filename);
168
- const skipExtensions = allowlist.skip_extensions ?? [".sql"];
169
- const skipPathPatterns = allowlist.skip_path_patterns ?? ["migrations/", "schema/"];
170
- const skipCommentMarkers = allowlist.skip_comment_markers ?? [
171
- "historical:",
172
- "migration:",
173
- "deprecated:",
174
- ];
175
- if (allowlist.skip_in_imports !== false &&
176
- /^import\s|^from\s|require\(/.test(trimmed)) {
177
- return true;
178
- }
179
- if (skipExtensions.includes(ext))
180
- return true;
181
- if (skipPathPatterns.some((pattern) => path.includes(pattern)))
182
- return true;
183
- if (skipCommentMarkers.some((marker) => trimmed.toLowerCase().includes(marker.toLowerCase()))) {
184
- return true;
185
- }
186
- if (/\/memory\//.test(path))
187
- return true;
188
- if (/RESEARCH\.md$|BRAND\.md$|CHANGELOG\.md$/.test(path))
189
- return true;
190
- if (/^\[.*\]\(.*\)/.test(trimmed) || /\]\(http/.test(trimmed))
191
- return true;
192
- if (SLUG_ONLY_PATTERNS.some((p) => p.test(trimmed)) &&
193
- !/[A-Z]/.test(trimmed.match(/(?:cognitive-debt|storyboard-studio|daydream-studio|shadow-ai-governance)/)?.[0] ?? "")) {
194
- return true;
195
- }
196
- if (/["'`/]/.test(trimmed)) {
197
- const inStringOrPath = /["'`/][^"'`]*(?:deployguard|storyboard-studio|daydream-studio|cognitive-debt|shadow-ai-governance|komatik-yggdrasil)[^"'`]*["'`/]/i;
198
- if (inStringOrPath.test(trimmed))
199
- return true;
200
- }
201
- return false;
202
- }
203
- export function detectContextFreshness(ctx) {
204
- if (!ctx.komatikInstance && ctx.staleTerms.length === 0)
205
- return null;
206
- const hits = [];
207
- for (const file of ctx.files) {
208
- if (isStaleArchivedPath(file.filename, ctx.pathIgnorePatterns))
209
- continue;
210
- for (const line of linesForFreshnessScan(file)) {
211
- if (isNamingAllowlisted(file.filename, line, ctx.namingAllowlist))
212
- continue;
213
- for (const term of ctx.staleTerms) {
214
- if (line.toLowerCase().includes(term.toLowerCase()))
215
- hits.push(file.filename);
216
- }
217
- if (ctx.komatikInstance) {
218
- for (const entry of OLD_NAME_PATTERNS) {
219
- entry.pattern.lastIndex = 0;
220
- if (entry.pattern.test(line))
221
- hits.push(file.filename);
222
- }
223
- }
224
- }
225
- }
226
- const unique = [...new Set(hits)];
227
- if (unique.length === 0)
228
- return null;
229
- return result({
230
- code: "context_freshness",
231
- severity: "warn",
232
- title: "Stale naming or deprecated terms",
233
- detail: `Added lines reference deprecated terms in ${unique.join(", ")}.`,
234
- files: unique,
235
- suggested_action: "Update naming to current product vocabulary (see BRAND.md).",
236
- autofix_eligible: true,
237
- });
238
- }
239
- export function detectPathFormat(ctx) {
240
- if (!ctx.komatikInstance)
241
- return null;
242
- const hits = ctx.files
243
- .map((f) => normalizePath(f.filename))
244
- .filter((name) => /^komatik-agents\/agents\//.test(name) ||
245
- /\/agents\/agents\//.test(name) ||
246
- (!/^agents\/[a-z][a-z0-9-]*\/suggestions\//.test(name) &&
247
- /\/suggestions\//.test(name) &&
248
- !name.startsWith("agents/")));
249
- if (hits.length === 0)
250
- return null;
251
- return result({
252
- code: "path_format",
253
- severity: "warn",
254
- title: "Suspicious agent suggestion path",
255
- detail: `Paths should match agents/<id>/suggestions/<project>/… — found: ${hits.join(", ")}.`,
256
- files: hits,
257
- suggested_action: "Use canonical agent suggestion paths without repo prefix.",
258
- });
259
- }
260
- export function detectHardcodedEnv(ctx) {
261
- const hits = scanAddedContent(ctx.files, (line, filename) => {
262
- if (/\.(md|txt)$/i.test(filename))
263
- return false;
264
- if (/^\s*\/\/|^\s*\*|^\s*#/.test(line))
265
- return false;
266
- return HARDCODED_ENV_PATTERNS.some((entry) => {
267
- entry.pattern.lastIndex = 0;
268
- return entry.pattern.test(line);
269
- });
270
- });
271
- if (hits.length === 0)
272
- return null;
273
- return result({
274
- code: "hardcoded_env",
275
- severity: "blocking",
276
- title: "Hardcoded environment value",
277
- detail: `Added lines contain hardcoded localhost/IP patterns in ${hits.join(", ")}.`,
278
- files: hits,
279
- suggested_action: "Use environment variables or configuration instead of hardcoded hosts.",
280
- });
281
- }
282
- export function detectLargeFile(ctx) {
283
- const hits = ctx.files
284
- .filter((file) => {
285
- const lines = typeof file.content === "string"
286
- ? file.content.split("\n").length
287
- : lineCountFromPatch(file.patch);
288
- return lines > ctx.maxFileLines;
289
- })
290
- .map((f) => f.filename);
291
- if (hits.length === 0)
292
- return null;
293
- return result({
294
- code: "large_file",
295
- severity: "warn",
296
- title: "Large file in PR",
297
- detail: `Files exceed ${ctx.maxFileLines} lines: ${hits.join(", ")}.`,
298
- files: hits,
299
- suggested_action: "Split large changes into smaller PRs.",
300
- });
301
- }
302
- function extractRelativeImports(content) {
303
- const imports = [];
304
- const patterns = [
305
- /\bimport\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"](\.\.?[^'"]+)['"]/g,
306
- /\brequire\(\s*['"](\.\.?[^'"]+)['"]\s*\)/g,
307
- ];
308
- for (const pattern of patterns) {
309
- for (const match of content.matchAll(pattern)) {
310
- if (match[1])
311
- imports.push(match[1]);
312
- }
313
- }
314
- return imports;
315
- }
316
- function resolveRelativeImport(fromFile, specifier, prPaths) {
317
- const clean = specifier.split("?")[0].split("#")[0];
318
- const baseDir = normalizePath(fromFile).split("/").slice(0, -1);
319
- const segments = clean.replace(/^\.\//, "").split("/");
320
- for (const segment of segments) {
321
- if (segment === "..")
322
- baseDir.pop();
323
- else if (segment !== ".")
324
- baseDir.push(segment);
325
- }
326
- const resolved = baseDir.join("/");
327
- const candidates = [
328
- resolved,
329
- `${resolved}.ts`,
330
- `${resolved}.tsx`,
331
- `${resolved}.js`,
332
- `${resolved}.jsx`,
333
- `${resolved}/index.ts`,
334
- `${resolved}/index.js`,
335
- ];
336
- return candidates.some((c) => prPaths.has(c));
337
- }
338
- export function detectImportResolution(ctx) {
339
- const codeExts = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
340
- const hits = [];
341
- for (const file of ctx.files) {
342
- if (!codeExts.has(extensionOf(file.filename)))
343
- continue;
344
- const content = fileContent(file);
345
- for (const specifier of extractRelativeImports(content)) {
346
- if (!resolveRelativeImport(file.filename, specifier, ctx.prPaths)) {
347
- hits.push(file.filename);
348
- break;
349
- }
350
- }
351
- }
352
- if (hits.length === 0)
353
- return null;
354
- return result({
355
- code: "import_resolution",
356
- severity: "blocking",
357
- title: "Unresolved relative import",
358
- detail: `Relative imports could not be resolved within this PR: ${[...new Set(hits)].join(", ")}.`,
359
- files: [...new Set(hits)],
360
- suggested_action: "Add missing files to the PR or fix import paths.",
361
- });
362
- }
363
- export function detectRlsNewTables(ctx) {
364
- const sqlFiles = ctx.files.filter((f) => extensionOf(f.filename) === ".sql");
365
- const corpus = sqlFiles.map((f) => fileContent(f)).join("\n");
366
- const hits = [];
367
- const createTable = /\bCREATE\s+(?!TEMP(?:ORARY)?\s+)TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"?[A-Za-z_][\w$]*"?\.)?"?[A-Za-z_][\w$]*"?)/gi;
368
- for (const file of sqlFiles) {
369
- const content = fileContent(file);
370
- for (const match of content.matchAll(createTable)) {
371
- const table = match[1]?.replace(/"/g, "") ?? "";
372
- const pattern = new RegExp(`ALTER\\s+TABLE\\s+(?:ONLY\\s+)?["']?${table.split(".").pop()}["']?\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`, "i");
373
- if (!pattern.test(corpus))
374
- hits.push(file.filename);
375
- }
376
- }
377
- if (hits.length === 0)
378
- return null;
379
- return result({
380
- code: "rls_new_tables",
381
- severity: "blocking",
382
- title: "New table missing RLS",
383
- detail: `CREATE TABLE without ENABLE ROW LEVEL SECURITY in ${[...new Set(hits)].join(", ")}.`,
384
- files: [...new Set(hits)],
385
- suggested_action: "Add ALTER TABLE ... ENABLE ROW LEVEL SECURITY for every new table.",
386
- });
387
- }
388
- function isRouteAllowlisted(path, allowlist) {
389
- return allowlist.some((entry) => path.includes(entry.replace(/^\//, "")));
390
- }
391
- export function detectAuthRouteAuth(ctx) {
392
- const routePattern = /(?:^|\/)(?:app\/api\/.+\/route|pages\/api\/.+)\.(?:ts|tsx|js|jsx)$/;
393
- const authPattern = /\b(getUser|getSession|getServerSession|auth|requireAuth|withAuth)\s*\(/;
394
- const hits = [];
395
- for (const file of ctx.files) {
396
- const normalized = normalizePath(file.filename);
397
- if (!routePattern.test(normalized))
398
- continue;
399
- if (isRouteAllowlisted(normalized, ctx.authRouteAllowlist))
400
- continue;
401
- if (!authPattern.test(fileContent(file)))
402
- hits.push(normalized);
403
- }
404
- if (hits.length === 0)
405
- return null;
406
- return result({
407
- code: "auth_route_auth",
408
- severity: "blocking",
409
- title: "API route missing auth check",
410
- detail: `Routes appear to lack session/user verification: ${hits.join(", ")}.`,
411
- files: hits,
412
- suggested_action: "Verify authenticated user before handling the request.",
413
- });
414
- }
415
- function packageNameFromSpecifier(specifier) {
416
- if (specifier.startsWith("@"))
417
- return specifier.split("/").slice(0, 2).join("/");
418
- return specifier.split("/")[0];
419
- }
420
- function isNodeBuiltin(specifier) {
421
- const bare = specifier.startsWith("node:") ? specifier.slice(5) : specifier;
422
- return NODE_BUILTINS.has(bare.split("/")[0]);
423
- }
424
- export function detectExternalPackageDeps(ctx) {
425
- if (ctx.declaredPackages.size === 0)
426
- return null;
427
- const codeExts = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
428
- const hits = [];
429
- for (const file of ctx.files) {
430
- if (!codeExts.has(extensionOf(file.filename)))
431
- continue;
432
- for (const imp of extractAllImports(fileContent(file))) {
433
- const specifier = imp.specifier;
434
- if (specifier.startsWith(".") || specifier.startsWith("/"))
435
- continue;
436
- if (specifier.startsWith("@/") || specifier.startsWith("~/"))
437
- continue;
438
- if (specifier.startsWith("http:") || specifier.startsWith("https:"))
439
- continue;
440
- if (isNodeBuiltin(specifier))
441
- continue;
442
- const pkg = packageNameFromSpecifier(specifier);
443
- if (!ctx.declaredPackages.has(pkg))
444
- hits.push(`${file.filename} → ${pkg}`);
445
- }
446
- }
447
- if (hits.length === 0)
448
- return null;
449
- return result({
450
- code: "external_package_deps",
451
- severity: "warn",
452
- title: "Undeclared package import",
453
- detail: hits.slice(0, 6).join("; "),
454
- files: [...new Set(hits.map((h) => h.split(" → ")[0]))],
455
- suggested_action: "Add the package to package.json or remove the import.",
456
- });
457
- }
458
- function stripSqlComments(content) {
459
- return content.replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
460
- }
461
- function countPlpgsqlBlockBegins(sql) {
462
- const re = /\bBEGIN\b(?!\s+(?:TRANSACTION|WORK)\b)/gi;
463
- return (sql.match(re) || []).length;
464
- }
465
- function countPlpgsqlBlockEnds(sql) {
466
- const re = /\bEND\s*(?!IF\b|LOOP\b|CASE\b)\s*(?:;|\$\$)/gi;
467
- return (sql.match(re) || []).length;
468
- }
469
- export function detectSqlSyntaxBasic(ctx) {
470
- const hits = [];
471
- for (const file of ctx.files.filter((f) => extensionOf(f.filename) === ".sql")) {
472
- const stripped = stripSqlComments(fileContent(file));
473
- const beginCount = countPlpgsqlBlockBegins(stripped);
474
- const endCount = countPlpgsqlBlockEnds(stripped);
475
- if (beginCount > 0 && beginCount > endCount)
476
- hits.push(file.filename);
477
- }
478
- if (hits.length === 0)
479
- return null;
480
- return result({
481
- code: "sql_syntax_basic",
482
- severity: "warn",
483
- title: "SQL block balance issue",
484
- detail: `Possible unclosed BEGIN block in ${hits.join(", ")}.`,
485
- files: hits,
486
- suggested_action: "Verify PL/pgSQL block structure before merging.",
487
- });
488
- }
489
- export function detectSyntaxValidity(ctx) {
490
- const errors = [];
491
- for (const file of ctx.files) {
492
- // Real parsers need the whole file — skip patch-only inputs (PR diff fragments).
493
- if (typeof file.content !== "string")
494
- continue;
495
- const message = validateFileSyntax(file.filename, file.content);
496
- if (message)
497
- errors.push(`${file.filename}: ${message}`);
498
- }
499
- if (errors.length === 0)
500
- return null;
501
- return result({
502
- code: "syntax_validity",
503
- severity: "blocking",
504
- title: "Syntax error in submitted file",
505
- detail: errors.slice(0, 12).join("; "),
506
- files: errors.map((e) => e.split(": ")[0] ?? e),
507
- suggested_action: "Fix the parse error before submitting.",
508
- });
509
- }
510
- export function detectSoulIntegrity(ctx) {
511
- if (!ctx.komatikInstance)
512
- return null;
513
- const hits = ctx.files
514
- .map((f) => normalizePath(f.filename))
515
- .filter((name) => /^agents\/[a-z][a-z0-9-]*\/SOUL\.md$/.test(name));
516
- if (hits.length === 0)
517
- return null;
518
- return result({
519
- code: "soul_integrity",
520
- severity: "blocking",
521
- title: "Agent SOUL.md modified",
522
- detail: `SOUL changes require human review: ${hits.join(", ")}.`,
523
- files: hits,
524
- suggested_action: "Revert SOUL.md changes or request explicit human approval.",
525
- });
526
- }
527
- export function runAllDetectors(ctx) {
528
- const gate1 = [
529
- detectMockPlaceholder(ctx),
530
- detectSecrets(ctx),
531
- detectDestructiveSql(ctx),
532
- detectSyntaxValidity(ctx),
533
- detectImportResolution(ctx),
534
- detectRlsNewTables(ctx),
535
- detectAuthRouteAuth(ctx),
536
- detectHardcodedEnv(ctx),
537
- detectExternalPackageDeps(ctx),
538
- detectSqlSyntaxBasic(ctx),
539
- detectLargeFile(ctx),
540
- detectArtifactIntegrity(ctx),
541
- detectContextFreshness(ctx),
542
- detectSoulIntegrity(ctx),
543
- detectPathFormat(ctx),
544
- ].filter((check) => check !== null);
545
- return [...gate1, ...runPhase0Detectors(ctx)];
546
- }
547
- //# sourceMappingURL=detectors.js.map