@aarock1234/slopscan 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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/SKILL.md +149 -0
  4. package/dist/analyzer.d.ts +5 -0
  5. package/dist/analyzer.js +13 -0
  6. package/dist/analyzer.js.map +1 -0
  7. package/dist/analyzers/ast.d.ts +12 -0
  8. package/dist/analyzers/ast.js +73 -0
  9. package/dist/analyzers/ast.js.map +1 -0
  10. package/dist/analyzers/judge-chunks.d.ts +13 -0
  11. package/dist/analyzers/judge-chunks.js +87 -0
  12. package/dist/analyzers/judge-chunks.js.map +1 -0
  13. package/dist/analyzers/judge-validate.d.ts +19 -0
  14. package/dist/analyzers/judge-validate.js +81 -0
  15. package/dist/analyzers/judge-validate.js.map +1 -0
  16. package/dist/analyzers/judge.d.ts +44 -0
  17. package/dist/analyzers/judge.js +177 -0
  18. package/dist/analyzers/judge.js.map +1 -0
  19. package/dist/change.d.ts +19 -0
  20. package/dist/change.js +87 -0
  21. package/dist/change.js.map +1 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.js +125 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +91 -0
  26. package/dist/config.js +61 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/confirm.d.ts +11 -0
  29. package/dist/confirm.js +100 -0
  30. package/dist/confirm.js.map +1 -0
  31. package/dist/finding.d.ts +13 -0
  32. package/dist/finding.js +18 -0
  33. package/dist/finding.js.map +1 -0
  34. package/dist/lang.d.ts +16 -0
  35. package/dist/lang.js +37 -0
  36. package/dist/lang.js.map +1 -0
  37. package/dist/model.d.ts +2 -0
  38. package/dist/model.js +34 -0
  39. package/dist/model.js.map +1 -0
  40. package/dist/report.d.ts +124 -0
  41. package/dist/report.js +112 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/rule.d.ts +66 -0
  44. package/dist/rule.js +259 -0
  45. package/dist/rule.js.map +1 -0
  46. package/dist/scan.d.ts +14 -0
  47. package/dist/scan.js +33 -0
  48. package/dist/scan.js.map +1 -0
  49. package/dist/score.d.ts +147 -0
  50. package/dist/score.js +142 -0
  51. package/dist/score.js.map +1 -0
  52. package/dist/shared/concurrency.d.ts +5 -0
  53. package/dist/shared/concurrency.js +17 -0
  54. package/dist/shared/concurrency.js.map +1 -0
  55. package/dist/shared/env.d.ts +27 -0
  56. package/dist/shared/env.js +29 -0
  57. package/dist/shared/env.js.map +1 -0
  58. package/dist/shared/errors.d.ts +18 -0
  59. package/dist/shared/errors.js +26 -0
  60. package/dist/shared/errors.js.map +1 -0
  61. package/dist/shared/log.d.ts +2 -0
  62. package/dist/shared/log.js +18 -0
  63. package/dist/shared/log.js.map +1 -0
  64. package/dist/shared/paths.d.ts +3 -0
  65. package/dist/shared/paths.js +6 -0
  66. package/dist/shared/paths.js.map +1 -0
  67. package/dist/shared/prompts.d.ts +3 -0
  68. package/dist/shared/prompts.js +34 -0
  69. package/dist/shared/prompts.js.map +1 -0
  70. package/package.json +79 -0
  71. package/prompts/judge.md +37 -0
  72. package/rules/any/futureproof/abstraction/pass-through-wrapper.md +70 -0
  73. package/rules/any/futureproof/abstraction/single-caller-helper.md +38 -0
  74. package/rules/any/futureproof/abstraction/single-impl-interface.md +83 -0
  75. package/rules/any/futureproof/exports/dead-export.md +62 -0
  76. package/rules/any/futureproof/layering/framework-type-in-domain.md +83 -0
  77. package/rules/any/futureproof/layering/logic-in-handler.md +94 -0
  78. package/rules/any/futureproof/params/boolean-positional-param.md +128 -0
  79. package/rules/any/futureproof/params/positional-config-args.md +135 -0
  80. package/rules/any/futureproof/structure/wide-function.md +138 -0
  81. package/rules/any/futureproof/testing/test-asserts-implementation.md +70 -0
  82. package/rules/any/hacky/comments/comment-restates-code.md +50 -0
  83. package/rules/any/hacky/comments/shipped-todo-comment.md +55 -0
  84. package/rules/any/hacky/concurrency/sleep-based-sync.md +95 -0
  85. package/rules/any/hacky/config/hardcoded-url.md +88 -0
  86. package/rules/any/hacky/constants/magic-number.md +94 -0
  87. package/rules/any/hacky/debugging/debug-print.md +73 -0
  88. package/rules/any/hacky/duplication/copy-paste-block.md +117 -0
  89. package/rules/any/hacky/types/stringly-typed-enum.md +86 -0
  90. package/rules/any/idiom/comments/no-section-banners.md +47 -0
  91. package/rules/go/futureproof/constants/string-enums-when-serialized.md +47 -0
  92. package/rules/go/futureproof/constants/typed-constants-for-enums.md +45 -0
  93. package/rules/go/futureproof/interfaces/compile-time-impl-assertion.md +43 -0
  94. package/rules/go/futureproof/interfaces/define-interfaces-at-consumer.md +47 -0
  95. package/rules/go/futureproof/interfaces/keep-interfaces-small.md +53 -0
  96. package/rules/go/futureproof/naming/no-utils-helpers-common-package.md +34 -0
  97. package/rules/go/futureproof/structs/zero-value-usable.md +50 -0
  98. package/rules/go/futureproof/types/generics-over-any.md +55 -0
  99. package/rules/go/hacky/concurrency/no-goroutine-without-wait.md +44 -0
  100. package/rules/go/hacky/context/never-pass-nil-context.md +64 -0
  101. package/rules/go/hacky/context/no-context-in-struct.md +44 -0
  102. package/rules/go/hacky/errors/check-close-error-on-writable.md +53 -0
  103. package/rules/go/hacky/errors/custom-error-type-errors-as.md +54 -0
  104. package/rules/go/hacky/errors/ignored-error-blank.md +84 -0
  105. package/rules/go/hacky/errors/panic-only-unrecoverable.md +77 -0
  106. package/rules/go/hacky/errors/sentinel-errors-with-errors-is.md +63 -0
  107. package/rules/go/hacky/errors/wrap-errors-with-w.md +71 -0
  108. package/rules/go/hacky/state/package-level-mutable-var.md +95 -0
  109. package/rules/go/hacky/structure/os-exit-only-in-main.md +61 -0
  110. package/rules/go/hacky/type-safety/no-map-string-any.md +49 -0
  111. package/rules/go/hacky/types/json-into-struct-not-map.md +48 -0
  112. package/rules/go/hacky/types/no-any.md +71 -0
  113. package/rules/go/hacky/types/no-map-any-any.md +35 -0
  114. package/rules/go/idiom/comments/doc-comment-on-exported.md +81 -0
  115. package/rules/go/idiom/comments/doc-comment-starts-with-name.md +36 -0
  116. package/rules/go/idiom/concurrency/mutex-over-channels-for-state.md +47 -0
  117. package/rules/go/idiom/constructors/constructor-named-new.md +37 -0
  118. package/rules/go/idiom/context/context-is-first-param.md +47 -0
  119. package/rules/go/idiom/context/io-takes-context.md +50 -0
  120. package/rules/go/idiom/errors/capitalized-error-message.md +52 -0
  121. package/rules/go/idiom/errors/check-error-immediately.md +45 -0
  122. package/rules/go/idiom/errors/defer-close-explicit-discard.md +65 -0
  123. package/rules/go/idiom/errors/error-is-last-return-value.md +53 -0
  124. package/rules/go/idiom/errors/no-log-and-return.md +45 -0
  125. package/rules/go/idiom/formatting/blank-line-before-return.md +48 -0
  126. package/rules/go/idiom/formatting/multiline-struct-literals.md +56 -0
  127. package/rules/go/idiom/imports/import-order-groups.md +42 -0
  128. package/rules/go/idiom/imports/side-effect-imports-own-group.md +51 -0
  129. package/rules/go/idiom/interfaces/accept-interfaces-return-concrete.md +39 -0
  130. package/rules/go/idiom/iterators/prefer-iter-seq.md +47 -0
  131. package/rules/go/idiom/logging/lowercase-log-messages.md +80 -0
  132. package/rules/go/idiom/logging/use-slog.md +46 -0
  133. package/rules/go/idiom/naming/acronyms-consistent-case.md +97 -0
  134. package/rules/go/idiom/naming/interface-er-suffix.md +33 -0
  135. package/rules/go/idiom/naming/no-package-name-stutter.md +37 -0
  136. package/rules/go/idiom/naming/package-name-single-lowercase-word.md +40 -0
  137. package/rules/go/idiom/receivers/consistent-receiver-kind-per-type.md +40 -0
  138. package/rules/go/idiom/receivers/no-this-receiver.md +37 -0
  139. package/rules/go/idiom/receivers/pointer-vs-value-receiver-choice.md +49 -0
  140. package/rules/go/idiom/receivers/short-receiver-names.md +43 -0
  141. package/rules/go/idiom/structs/new-expr-for-pointer-fields.md +40 -0
  142. package/rules/go/idiom/structure/main-delegates-to-run.md +54 -0
  143. package/rules/ts/futureproof/classes/composition-over-abstract-base.md +73 -0
  144. package/rules/ts/futureproof/functions/explicit-return-type-on-exports.md +50 -0
  145. package/rules/ts/futureproof/structure/colocate-zod-schemas.md +46 -0
  146. package/rules/ts/futureproof/types/exhaustive-switch-never-check.md +52 -0
  147. package/rules/ts/futureproof/types/readonly-for-immutable-data.md +46 -0
  148. package/rules/ts/futureproof/zod/enum-values-from-const-object.md +37 -0
  149. package/rules/ts/futureproof/zod/no-z-native-enum.md +36 -0
  150. package/rules/ts/futureproof/zod/schema-first-infer-type.md +46 -0
  151. package/rules/ts/hacky/async/no-floating-promises.md +52 -0
  152. package/rules/ts/hacky/config/env-read-outside-config.md +56 -0
  153. package/rules/ts/hacky/errors/catch-param-typed-unknown.md +67 -0
  154. package/rules/ts/hacky/errors/custom-error-class-instanceof.md +66 -0
  155. package/rules/ts/hacky/errors/empty-catch.md +52 -0
  156. package/rules/ts/hacky/errors/log-and-swallow.md +65 -0
  157. package/rules/ts/hacky/errors/throw-typed-error-with-context.md +64 -0
  158. package/rules/ts/hacky/operators/nullish-coalescing-over-or.md +50 -0
  159. package/rules/ts/hacky/state/exported-let.md +60 -0
  160. package/rules/ts/hacky/type-safety/no-double-assertion.md +34 -0
  161. package/rules/ts/hacky/type-safety/no-explicit-any.md +48 -0
  162. package/rules/ts/hacky/type-safety/no-non-null-assertion.md +43 -0
  163. package/rules/ts/hacky/type-safety/no-unchecked-type-assertion.md +54 -0
  164. package/rules/ts/hacky/type-safety/validate-parsed-json.md +48 -0
  165. package/rules/ts/idiom/async/no-promise-chains.md +45 -0
  166. package/rules/ts/idiom/async/parallelize-independent-awaits.md +46 -0
  167. package/rules/ts/idiom/comments/doc-comment-above-declaration.md +43 -0
  168. package/rules/ts/idiom/constants/as-const-for-literal-config.md +65 -0
  169. package/rules/ts/idiom/constants/no-enum.md +37 -0
  170. package/rules/ts/idiom/control-flow/always-brace-if.md +56 -0
  171. package/rules/ts/idiom/control-flow/guard-clauses-early-return.md +52 -0
  172. package/rules/ts/idiom/control-flow/no-nested-ternary.md +54 -0
  173. package/rules/ts/idiom/errors/error-message-lowercase.md +56 -0
  174. package/rules/ts/idiom/errors/log-at-boundary-not-every-layer.md +66 -0
  175. package/rules/ts/idiom/exports/inline-export-at-declaration.md +49 -0
  176. package/rules/ts/idiom/exports/no-default-export.md +37 -0
  177. package/rules/ts/idiom/formatting/multiline-object-literals.md +46 -0
  178. package/rules/ts/idiom/functions/arrow-for-callbacks.md +44 -0
  179. package/rules/ts/idiom/functions/function-declaration-for-top-level.md +56 -0
  180. package/rules/ts/idiom/imports/export-type-for-types.md +34 -0
  181. package/rules/ts/idiom/imports/import-order.md +66 -0
  182. package/rules/ts/idiom/imports/import-type-for-types.md +50 -0
  183. package/rules/ts/idiom/imports/side-effect-imports-last.md +42 -0
  184. package/rules/ts/idiom/logging/structured-logging-lowercase.md +45 -0
  185. package/rules/ts/idiom/naming/boolean-name-question-prefix.md +42 -0
  186. package/rules/ts/idiom/naming/descriptive-identifier-quality.md +48 -0
  187. package/rules/ts/idiom/naming/screaming-snake-module-constants.md +58 -0
  188. package/rules/ts/idiom/strings/template-literals-over-concat.md +51 -0
  189. package/rules/ts/idiom/types/interface-extends-over-intersection.md +44 -0
  190. package/rules/ts/idiom/types/null-vs-undefined-convention.md +43 -0
  191. package/rules/ts/idiom/types/prefer-type-over-interface.md +64 -0
  192. package/rules/ts/idiom/types/satisfies-over-annotation.md +43 -0
  193. package/rules/ts/idiom/types/type-predicates-for-narrowing.md +53 -0
  194. package/rules/ts/idiom/variables/const-by-default.md +41 -0
  195. package/rules/ts/idiom/variables/no-var.md +36 -0
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+ // how far the model's reported line may drift from where the quote is actually found
3
+ const LINE_TOLERANCE = 3;
4
+ const MAX_QUOTE_LENGTH = 400;
5
+ const MAX_MESSAGE_LENGTH = 240;
6
+ export function judgeOutputSchema(ruleIds) {
7
+ return z.object({
8
+ findings: z.array(z.object({
9
+ ruleId: z.enum(ruleIds),
10
+ line: z.number().int().positive(),
11
+ quote: z.string().min(1).max(MAX_QUOTE_LENGTH),
12
+ message: z.string().max(MAX_MESSAGE_LENGTH),
13
+ confidence: z.number().min(0).max(1),
14
+ symbol: z.string().nullable(),
15
+ })),
16
+ });
17
+ }
18
+ // keeps only findings whose quote really appears in the file, on a changed line, near the reported line,
19
+ // with enough confidence. one survivor per rule per span.
20
+ export function validateFindings(raw, change, rules, minConfidence) {
21
+ const lines = change.source.split('\n');
22
+ const normalized = lines.map(normalize);
23
+ const located = raw.flatMap(candidate => {
24
+ const rule = rules.get(candidate.ruleId);
25
+ if (rule === undefined || candidate.confidence < minConfidence) {
26
+ return [];
27
+ }
28
+ const line = locate(candidate, normalized);
29
+ if (line === undefined || !change.changedLines.has(line)) {
30
+ return [];
31
+ }
32
+ const quoteLines = candidate.quote.split('\n').length;
33
+ const symbol = candidate.symbol?.trim();
34
+ const finding = {
35
+ ruleId: candidate.ruleId,
36
+ path: change.path,
37
+ line,
38
+ endLine: Math.min(lines.length, line + quoteLines - 1),
39
+ quote: lines.slice(line - 1, line - 1 + quoteLines).join('\n'),
40
+ message: candidate.message.trim() || rule.message,
41
+ confidence: candidate.confidence,
42
+ ...(symbol && { symbol }),
43
+ };
44
+ return [finding];
45
+ });
46
+ return dedupe(located);
47
+ }
48
+ // the line whose text contains the quote's first line, closest to where the model said it was
49
+ function locate(candidate, normalizedLines) {
50
+ const needle = normalize(candidate.quote.split('\n')[0] ?? '');
51
+ if (needle.length === 0) {
52
+ return undefined;
53
+ }
54
+ let best;
55
+ for (let offset = 0; offset <= LINE_TOLERANCE; offset += 1) {
56
+ for (const line of [candidate.line - offset, candidate.line + offset]) {
57
+ if (normalizedLines[line - 1]?.includes(needle)) {
58
+ best ??= line;
59
+ }
60
+ }
61
+ if (best !== undefined) {
62
+ return best;
63
+ }
64
+ }
65
+ return undefined;
66
+ }
67
+ // same rule, overlapping span: keep the most confident
68
+ function dedupe(findings) {
69
+ const kept = [];
70
+ for (const finding of [...findings].sort((a, b) => b.confidence - a.confidence)) {
71
+ const overlaps = kept.some(other => other.ruleId === finding.ruleId && other.line <= finding.endLine && finding.line <= other.endLine);
72
+ if (!overlaps) {
73
+ kept.push(finding);
74
+ }
75
+ }
76
+ return kept;
77
+ }
78
+ function normalize(text) {
79
+ return text.replace(/\s+/g, ' ').trim();
80
+ }
81
+ //# sourceMappingURL=judge-validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge-validate.js","sourceRoot":"","sources":["../../src/analyzers/judge-validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { LanguageModel } from 'ai';
2
+ import { z } from 'zod';
3
+ import type { Analyzer } from '../analyzer.js';
4
+ export declare const judgeConfigSchema: z.ZodObject<{
5
+ model: z.ZodDefault<z.ZodString>;
6
+ maxInputTokens: z.ZodDefault<z.ZodNumber>;
7
+ minChangedLines: z.ZodDefault<z.ZodNumber>;
8
+ chunkTokens: z.ZodDefault<z.ZodNumber>;
9
+ minConfidence: z.ZodDefault<z.ZodNumber>;
10
+ maxOutputTokens: z.ZodDefault<z.ZodNumber>;
11
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
12
+ }, z.core.$strict>;
13
+ export type JudgeConfig = z.infer<typeof judgeConfigSchema>;
14
+ export declare const SkipReason: {
15
+ readonly BUDGET: "budget";
16
+ readonly TOO_SMALL: "too-small";
17
+ };
18
+ export type SkipReason = (typeof SkipReason)[keyof typeof SkipReason];
19
+ export declare const judgeSummarySchema: z.ZodObject<{
20
+ model: z.ZodString;
21
+ promptVersion: z.ZodString;
22
+ calls: z.ZodNumber;
23
+ cachedCalls: z.ZodNumber;
24
+ inputTokens: z.ZodNumber;
25
+ outputTokens: z.ZodNumber;
26
+ skipped: z.ZodArray<z.ZodObject<{
27
+ path: z.ZodString;
28
+ reason: z.ZodEnum<{
29
+ budget: "budget";
30
+ "too-small": "too-small";
31
+ }>;
32
+ }, z.core.$strip>>;
33
+ }, z.core.$strip>;
34
+ export type JudgeSummary = z.infer<typeof judgeSummarySchema>;
35
+ export type Judge = {
36
+ analyze: Analyzer;
37
+ summary(): JudgeSummary;
38
+ };
39
+ export type JudgeOptions = {
40
+ config: JudgeConfig;
41
+ model: LanguageModel;
42
+ cacheDir: string;
43
+ };
44
+ export declare function createJudge(options: JudgeOptions): Judge;
@@ -0,0 +1,177 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { Output, generateText } from 'ai';
5
+ import { z } from 'zod';
6
+ import { Detect } from '../rule.js';
7
+ import { logger } from '../shared/log.js';
8
+ import { loadPrompt } from '../shared/prompts.js';
9
+ import { chunkChange } from './judge-chunks.js';
10
+ import { judgeOutputSchema, validateFindings } from './judge-validate.js';
11
+ export const judgeConfigSchema = z
12
+ .object({
13
+ // "<provider>/<model id>"; see model.ts for providers
14
+ model: z.string().default('openai/gpt-6-sol'),
15
+ // total prompt tokens one run may spend before it starts skipping files
16
+ maxInputTokens: z.number().int().positive().default(500_000),
17
+ // files with fewer changed lines than this skip the judge
18
+ minChangedLines: z.number().int().positive().default(3),
19
+ // prompt tokens of code per model call
20
+ chunkTokens: z.number().int().positive().default(6000),
21
+ minConfidence: z.number().min(0).max(1).default(0.6),
22
+ maxOutputTokens: z.number().int().positive().default(2000),
23
+ // wall-clock cap per model call, retries included
24
+ timeoutMs: z.number().int().positive().default(90_000),
25
+ })
26
+ .strict();
27
+ export const SkipReason = {
28
+ BUDGET: 'budget',
29
+ TOO_SMALL: 'too-small',
30
+ };
31
+ export const judgeSummarySchema = z.object({
32
+ model: z.string(),
33
+ promptVersion: z.string(),
34
+ calls: z.number().int().nonnegative(),
35
+ cachedCalls: z.number().int().nonnegative(),
36
+ inputTokens: z.number().int().nonnegative(),
37
+ outputTokens: z.number().int().nonnegative(),
38
+ skipped: z.array(z.object({
39
+ path: z.string(),
40
+ reason: z.enum([SkipReason.BUDGET, SkipReason.TOO_SMALL]),
41
+ })),
42
+ });
43
+ const PROMPT_NAME = 'judge';
44
+ const PROMPT_VERSION_LENGTH = 12;
45
+ // one model call per chunk of changed code, with the judge rules for that language as the rubric.
46
+ // results are cached by content so the same head commit always produces the same findings.
47
+ export function createJudge(options) {
48
+ let remainingTokens = options.config.maxInputTokens;
49
+ const summary = {
50
+ model: options.config.model,
51
+ promptVersion: '',
52
+ calls: 0,
53
+ cachedCalls: 0,
54
+ inputTokens: 0,
55
+ outputTokens: 0,
56
+ skipped: [],
57
+ };
58
+ async function analyze(change, rules) {
59
+ const judgeRules = rules.filter((rule) => rule.detect === Detect.JUDGE);
60
+ const ruleIds = judgeRules.map(rule => rule.id);
61
+ if (!isNonEmpty(ruleIds)) {
62
+ return [];
63
+ }
64
+ if (change.changedLines.size < options.config.minChangedLines) {
65
+ summary.skipped.push({ path: change.path, reason: SkipReason.TOO_SMALL });
66
+ return [];
67
+ }
68
+ const astRuleIds = rules.filter(rule => rule.detect === Detect.AST).map(rule => rule.id);
69
+ const system = loadPrompt(PROMPT_NAME, {
70
+ RULES: renderRules(judgeRules),
71
+ HANDLED_BY_AST: astRuleIds.length > 0 ? astRuleIds.join(', ') : 'none',
72
+ });
73
+ summary.promptVersion = hash(system).slice(0, PROMPT_VERSION_LENGTH);
74
+ const schema = judgeOutputSchema(ruleIds);
75
+ const rulesById = new Map(judgeRules.map(rule => [rule.id, rule]));
76
+ const raw = [];
77
+ for (const chunk of chunkChange(change, options.config.chunkTokens)) {
78
+ const estimated = chunk.estimatedTokens + estimateSystemTokens(system);
79
+ if (estimated > remainingTokens) {
80
+ summary.skipped.push({ path: change.path, reason: SkipReason.BUDGET });
81
+ break;
82
+ }
83
+ const output = await judgeChunk(chunk, system, schema);
84
+ raw.push(...output.findings);
85
+ }
86
+ return validateFindings(raw, change, rulesById, options.config.minConfidence);
87
+ }
88
+ async function judgeChunk(chunk, system, schema) {
89
+ const key = hash(`${options.config.model}\n${system}\n${chunk.text}`);
90
+ const cached = await readCache(options.cacheDir, key, schema);
91
+ if (cached !== undefined) {
92
+ summary.cachedCalls += 1;
93
+ logger.debug({ path: chunk.path, cached: true, findings: cached.findings }, 'judge output');
94
+ return cached;
95
+ }
96
+ const result = await generateText({
97
+ model: options.model,
98
+ output: Output.object({ schema, name: 'findings' }),
99
+ system,
100
+ prompt: chunk.text,
101
+ temperature: 0,
102
+ maxRetries: 2,
103
+ maxOutputTokens: options.config.maxOutputTokens,
104
+ timeout: options.config.timeoutMs,
105
+ });
106
+ summary.calls += 1;
107
+ summary.inputTokens += result.usage.inputTokens ?? 0;
108
+ summary.outputTokens += result.usage.outputTokens ?? 0;
109
+ remainingTokens -= result.usage.inputTokens ?? chunk.estimatedTokens;
110
+ logger.debug({ path: chunk.path, cached: false, findings: result.output.findings }, 'judge output');
111
+ await writeCache(options.cacheDir, key, result.output);
112
+ return result.output;
113
+ }
114
+ return {
115
+ analyze,
116
+ summary: () => structuredClone(summary),
117
+ };
118
+ }
119
+ // the rubric: id, message, why, the shortest bad and good example, and known false positives
120
+ function renderRules(rules) {
121
+ return rules
122
+ .map(rule => {
123
+ const bad = shortest(rule.bad.map(example => example.source));
124
+ const good = shortest(rule.good.map(example => example.source));
125
+ const falsePositives = rule.falsePositives.length > 0
126
+ ? `\nNot a violation:\n${rule.falsePositives.map(item => `- ${item}`).join('\n')}`
127
+ : '';
128
+ const symbol = rule.confirm ? '\nProvide `symbol`: the name this finding is about.' : '';
129
+ return [
130
+ `### ${rule.id}`,
131
+ `Message: ${rule.message}`,
132
+ rule.why,
133
+ `Bad:\n\`\`\`\n${bad}\n\`\`\``,
134
+ `Good:\n\`\`\`\n${good}\n\`\`\`${falsePositives}${symbol}`,
135
+ ].join('\n\n');
136
+ })
137
+ .join('\n\n');
138
+ }
139
+ function shortest(sources) {
140
+ return sources.reduce((best, source) => (source.length < best.length ? source : best), sources[0] ?? '');
141
+ }
142
+ const systemTokenCache = new Map();
143
+ function estimateSystemTokens(system) {
144
+ const cached = systemTokenCache.get(system);
145
+ if (cached !== undefined) {
146
+ return cached;
147
+ }
148
+ const estimate = Math.ceil(system.length / 4);
149
+ systemTokenCache.set(system, estimate);
150
+ return estimate;
151
+ }
152
+ async function readCache(dir, key, schema) {
153
+ try {
154
+ const parsed = schema.safeParse(JSON.parse(await readFile(join(dir, `${key}.json`), 'utf-8')));
155
+ return parsed.success ? parsed.data : undefined;
156
+ }
157
+ catch (error) {
158
+ if (isMissingFile(error)) {
159
+ return undefined;
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ async function writeCache(dir, key, output) {
165
+ await mkdir(dir, { recursive: true });
166
+ await writeFile(join(dir, `${key}.json`), JSON.stringify(output, null, 2));
167
+ }
168
+ function isMissingFile(error) {
169
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
170
+ }
171
+ function hash(text) {
172
+ return createHash('sha256').update(text).digest('hex');
173
+ }
174
+ function isNonEmpty(items) {
175
+ return items.length > 0;
176
+ }
177
+ //# sourceMappingURL=judge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"judge.js","sourceRoot":"","sources":["../../src/analyzers/judge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Lang } from './lang.js';
2
+ export type Range = {
3
+ base: string;
4
+ head: string;
5
+ };
6
+ export type Change = {
7
+ path: string;
8
+ lang: Lang;
9
+ source: string;
10
+ changedLines: ReadonlySet<number>;
11
+ scoredLines: number;
12
+ };
13
+ export declare function readChanges(repo: string, range: Range, ignore: readonly string[]): Promise<Change[]>;
14
+ export declare function isIgnored(path: string, globs: readonly string[]): boolean;
15
+ export type DiffFile = {
16
+ path: string;
17
+ addedLines: readonly number[];
18
+ };
19
+ export declare function parseDiff(diff: string): DiffFile[];
package/dist/change.js ADDED
@@ -0,0 +1,87 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { matchesGlob } from 'node:path';
3
+ import { promisify } from 'node:util';
4
+ import { detectLang } from './lang.js';
5
+ import { mapConcurrent } from './shared/concurrency.js';
6
+ import { GitError } from './shared/errors.js';
7
+ const execFileAsync = promisify(execFile);
8
+ const GIT_MAX_BUFFER = 64 * 1024 * 1024;
9
+ export async function readChanges(repo, range, ignore) {
10
+ const diff = await git(repo, [
11
+ 'diff',
12
+ '--unified=0',
13
+ '--no-color',
14
+ '--diff-filter=AMR',
15
+ '--find-renames',
16
+ `${range.base}...${range.head}`,
17
+ ]);
18
+ const files = parseDiff(diff).filter(file => detectLang(file.path) !== undefined && !isIgnored(file.path, ignore));
19
+ return mapConcurrent(files, async (file) => {
20
+ const source = await git(repo, ['show', `${range.head}:${file.path}`]);
21
+ return toChange(file, source);
22
+ });
23
+ }
24
+ export function isIgnored(path, globs) {
25
+ return globs.some(glob => matchesGlob(path, glob));
26
+ }
27
+ const NEW_FILE_HEADER = '+++ b/';
28
+ const DELETED_FILE_HEADER = '+++ /dev/null';
29
+ const HUNK_HEADER_PATTERN = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/;
30
+ // reads a zero-context unified diff. with no context lines every hunk's `+` side is exactly the added
31
+ // or modified lines, so the header alone tells us which new-file lines changed.
32
+ export function parseDiff(diff) {
33
+ const files = [];
34
+ let current;
35
+ for (const line of diff.split('\n')) {
36
+ if (line.startsWith(DELETED_FILE_HEADER)) {
37
+ current = undefined;
38
+ continue;
39
+ }
40
+ if (line.startsWith(NEW_FILE_HEADER)) {
41
+ current = { path: line.slice(NEW_FILE_HEADER.length).trim(), addedLines: [] };
42
+ files.push(current);
43
+ continue;
44
+ }
45
+ const hunk = HUNK_HEADER_PATTERN.exec(line);
46
+ if (hunk === null || current === undefined) {
47
+ continue;
48
+ }
49
+ const start = Number(hunk[1]);
50
+ const count = hunk[2] === undefined ? 1 : Number(hunk[2]);
51
+ for (let offset = 0; offset < count; offset += 1) {
52
+ current.addedLines.push(start + offset);
53
+ }
54
+ }
55
+ return files;
56
+ }
57
+ function toChange(file, source) {
58
+ const lang = detectLang(file.path);
59
+ if (lang === undefined) {
60
+ throw new Error(`no language for ${file.path}`);
61
+ }
62
+ const lines = source.split('\n');
63
+ const changedLines = new Set(file.addedLines);
64
+ const scoredLines = [...changedLines].filter(line => lines[line - 1]?.trim()).length;
65
+ return {
66
+ path: file.path,
67
+ lang,
68
+ source,
69
+ changedLines,
70
+ scoredLines,
71
+ };
72
+ }
73
+ async function git(repo, args) {
74
+ try {
75
+ const { stdout } = await execFileAsync('git', [...args], {
76
+ cwd: repo,
77
+ encoding: 'utf-8',
78
+ maxBuffer: GIT_MAX_BUFFER,
79
+ });
80
+ return stdout;
81
+ }
82
+ catch (error) {
83
+ const stderr = error instanceof Error && 'stderr' in error ? String(error.stderr).trim() : '';
84
+ throw new GitError(`git ${args[0] ?? ''} failed${stderr ? `: ${stderr}` : ''}`, { cause: error });
85
+ }
86
+ }
87
+ //# sourceMappingURL=change.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change.js","sourceRoot":"","sources":["../src/change.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import { createJudge } from './analyzers/judge.js';
6
+ import { loadConfig } from './config.js';
7
+ import { resolveModel } from './model.js';
8
+ import { Format, formatValues, isFormat, renderReport } from './report.js';
9
+ import { loadRules } from './rule.js';
10
+ import { scan } from './scan.js';
11
+ import { SlopscanError } from './shared/errors.js';
12
+ import { logger } from './shared/log.js';
13
+ import { RULES_DIR } from './shared/paths.js';
14
+ const HELP = `slopscan - scores a git diff for slop
15
+
16
+ usage:
17
+ slopscan scan [--base <ref>] [--head <ref>] [--baseline <ref>] [--format <fmt>] [--no-judge] [--config <path>]
18
+ slopscan rules
19
+
20
+ options:
21
+ --base <ref> ref to diff against (default: main)
22
+ --head <ref> ref to score (default: HEAD)
23
+ --baseline <ref> also score baseline..base and show the delta
24
+ --format <fmt> ${formatValues.join(', ')} (default: ${Format.TERMINAL})
25
+ --no-judge skip the LLM judge; syntax rules only, no API key needed
26
+ --config <path> path to .slopscan.yml (default: ./.slopscan.yml)
27
+ --rules <dir> rule directory (default: the bundled rules)
28
+ --json-out <path> also write the full report as json to this file
29
+ -h, --help show this help
30
+
31
+ exit codes: 0 under the fail threshold, 1 over it, 2 on error
32
+ `;
33
+ const CACHE_DIR = '.slopscan-cache';
34
+ const ExitCode = {
35
+ OK: 0,
36
+ FAILED_THRESHOLD: 1,
37
+ ERROR: 2,
38
+ };
39
+ const Command = {
40
+ SCAN: 'scan',
41
+ RULES: 'rules',
42
+ };
43
+ const parseOptions = {
44
+ options: {
45
+ base: { type: 'string', default: 'main' },
46
+ head: { type: 'string', default: 'HEAD' },
47
+ baseline: { type: 'string' },
48
+ format: { type: 'string', default: Format.TERMINAL },
49
+ judge: { type: 'boolean', default: true },
50
+ config: { type: 'string' },
51
+ rules: { type: 'string', default: RULES_DIR },
52
+ 'json-out': { type: 'string' },
53
+ help: { type: 'boolean', short: 'h', default: false },
54
+ },
55
+ allowPositionals: true,
56
+ allowNegative: true,
57
+ strict: true,
58
+ };
59
+ async function main(argv) {
60
+ const { values, positionals } = parseArgs({ ...parseOptions, args: [...argv] });
61
+ const command = positionals[0];
62
+ if (values.help || command === undefined) {
63
+ process.stdout.write(HELP);
64
+ return ExitCode.OK;
65
+ }
66
+ switch (command) {
67
+ case Command.SCAN:
68
+ return runScan(values);
69
+ case Command.RULES:
70
+ return listRules(values.rules);
71
+ default:
72
+ throw new Error(`unknown command "${command}"`);
73
+ }
74
+ }
75
+ async function runScan(values) {
76
+ if (!isFormat(values.format)) {
77
+ throw new Error(`unknown format "${values.format}"; expected one of ${formatValues.join(', ')}`);
78
+ }
79
+ const repo = process.cwd();
80
+ const [config, rules] = await Promise.all([loadConfig(repo, values.config), loadRules(values.rules)]);
81
+ const judge = values.judge ? buildJudge(repo, config) : undefined;
82
+ const report = await scan({
83
+ repo,
84
+ range: { base: values.base, head: values.head },
85
+ config,
86
+ rules,
87
+ ...(judge && { judge }),
88
+ ...(values.baseline !== undefined && { baseline: values.baseline }),
89
+ });
90
+ process.stdout.write(renderReport(report, { format: values.format, color: process.stdout.isTTY }));
91
+ if (values['json-out'] !== undefined) {
92
+ await writeFile(values['json-out'], renderReport(report, { format: Format.JSON, color: false }));
93
+ }
94
+ logger.debug({ files: report.files, findings: report.findings.length, overall: report.overall }, 'scan complete');
95
+ return report.overall > config.failThreshold ? ExitCode.FAILED_THRESHOLD : ExitCode.OK;
96
+ }
97
+ function buildJudge(repo, config) {
98
+ return createJudge({
99
+ config: config.judge,
100
+ model: resolveModel(config.judge.model),
101
+ cacheDir: join(repo, CACHE_DIR, 'judge'),
102
+ });
103
+ }
104
+ async function listRules(rulesDir) {
105
+ const rules = await loadRules(rulesDir);
106
+ for (const rule of rules) {
107
+ process.stdout.write(`${rule.id.padEnd(44)} ${rule.severity.padEnd(8)} ${rule.detect.padEnd(5)} ${rule.message}\n`);
108
+ }
109
+ return ExitCode.OK;
110
+ }
111
+ main(process.argv.slice(2))
112
+ .then(code => {
113
+ process.exitCode = code;
114
+ })
115
+ .catch((error) => {
116
+ // our own errors carry a readable message; anything else is a bug and deserves the stack
117
+ if (error instanceof SlopscanError) {
118
+ process.stderr.write(`error: ${error.message}\n`);
119
+ }
120
+ else {
121
+ logger.error({ err: error }, 'slopscan failed');
122
+ }
123
+ process.exitCode = ExitCode.ERROR;
124
+ });
125
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}