@eviano/tribunal 0.1.2 → 0.2.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 (41) hide show
  1. package/README.md +177 -11
  2. package/dist/analyzers/claimReconciliation.js +43 -22
  3. package/dist/analyzers/claimReconciliation.js.map +1 -1
  4. package/dist/analyzers/commentCodeDrift.d.ts +37 -0
  5. package/dist/analyzers/commentCodeDrift.js +241 -0
  6. package/dist/analyzers/commentCodeDrift.js.map +1 -0
  7. package/dist/analyzers/hallucinatedSymbol.js +22 -0
  8. package/dist/analyzers/hallucinatedSymbol.js.map +1 -1
  9. package/dist/analyzers/index.d.ts +5 -1
  10. package/dist/analyzers/index.js +12 -2
  11. package/dist/analyzers/index.js.map +1 -1
  12. package/dist/analyzers/riskyDiffNoTest.d.ts +36 -0
  13. package/dist/analyzers/riskyDiffNoTest.js +221 -0
  14. package/dist/analyzers/riskyDiffNoTest.js.map +1 -0
  15. package/dist/cli.js +167 -24
  16. package/dist/cli.js.map +1 -1
  17. package/dist/config/loadConfig.d.ts +19 -0
  18. package/dist/config/loadConfig.js +89 -0
  19. package/dist/config/loadConfig.js.map +1 -0
  20. package/dist/config/parseYaml.d.ts +17 -0
  21. package/dist/config/parseYaml.js +131 -0
  22. package/dist/config/parseYaml.js.map +1 -0
  23. package/dist/config/types.d.ts +17 -0
  24. package/dist/config/types.js +2 -0
  25. package/dist/config/types.js.map +1 -0
  26. package/dist/diff/parseUnifiedDiff.d.ts +4 -0
  27. package/dist/diff/parseUnifiedDiff.js +45 -18
  28. package/dist/diff/parseUnifiedDiff.js.map +1 -1
  29. package/dist/index.d.ts +3 -2
  30. package/dist/index.js +3 -2
  31. package/dist/index.js.map +1 -1
  32. package/dist/paths.d.ts +27 -0
  33. package/dist/paths.js +112 -0
  34. package/dist/paths.js.map +1 -0
  35. package/dist/propose.d.ts +103 -0
  36. package/dist/propose.js +175 -0
  37. package/dist/propose.js.map +1 -0
  38. package/dist/report/render.d.ts +5 -0
  39. package/dist/report/render.js +78 -0
  40. package/dist/report/render.js.map +1 -1
  41. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,30 +1,65 @@
1
1
  #!/usr/bin/env node
2
- import { runTribunal } from './index.js';
2
+ import { runTribunal, analyzers as defaultAnalyzers } from './index.js';
3
3
  import { exitCode, renderJson, renderMarkdown } from './report/render.js';
4
4
  import { parseClaims } from './claims.js';
5
- import { readFileSync } from 'node:fs';
5
+ import { readFileSync, writeFileSync } from 'node:fs';
6
+ import { runPropose } from './propose.js';
7
+ import { renderSarif } from './report/render.js';
8
+ import { setSkipGenerated, setExtraGeneratedPaths } from './analyzers/riskyDiffNoTest.js';
9
+ import { loadConfig, setKnownAnalyzerIds } from './config/loadConfig.js';
10
+ // Register the known analyzer ids so loadConfig can validate `analyzers:` keys (fail-loud on typos).
11
+ setKnownAnalyzerIds(defaultAnalyzers.map((a) => a.id));
6
12
  const HELP = `tribunal — a deterministic, no-LLM CI gate for agent-authored PRs
7
13
 
8
14
  Usage:
9
- tribunal check [options]
15
+ tribunal check [options] Deterministically verify a PR (default).
16
+ tribunal propose [options] Ask an LLM to PROPOSE claims; never adjudicates.
10
17
 
11
- Options:
18
+ check — options:
12
19
  --base <ref> Base ref for a range diff (e.g. the PR base branch).
13
20
  --head <ref> Head ref for a range diff. Defaults to working tree vs HEAD.
14
21
  --diff <file> Read a unified diff from a file instead of invoking git.
15
22
  --claims <file> Read machine-readable claims from a file (whole file, or a tribunal fenced block).
16
23
  --pr-body <file> Read claims ONLY from a \`\`\`tribunal fenced block in a PR-body file.
17
24
  --cwd <dir> Repo root to run in (default: current directory).
18
- --format <fmt> Output format: md (default) or json.
25
+ --format <fmt> Output format: md (default), json, or sarif (upload via github/codeql-action/upload-sarif).
19
26
  --hard-fail Exit non-zero when there is at least one CONTRADICTED finding.
20
27
  Off by default (report-only). Gates ONLY on CONTRADICTED.
21
- -h, --help Show this help.
28
+ --no-skip-generated Don't skip generated/build-output paths in risky-diff-no-test
29
+ (dist/, *.min.js, …). Default skips them; this re-enables flagging them.
30
+ --config <file> Path to tribunal.yml (default: <repo-root>/tribunal.yml). Configures per-analyzer
31
+ enable/disable and extra generated-path patterns. Absent = all defaults.
32
+
33
+ propose — options:
34
+ --diff <file> Diff to propose claims for (required for propose).
35
+ --pr-body <file> Optional PR body to give the model extra context.
36
+ --endpoint <url> OpenAI-compatible chat-completions base URL (env: TRIBUNAL_ENDPOINT).
37
+ --model <name> Model name (env: TRIBUNAL_MODEL).
38
+ --api-key <key> Bearer token (env: TRIBUNAL_API_KEY).
39
+ --allow-send-diff Actually send the diff to the endpoint. WITHOUT this flag, propose prints the
40
+ prompt and sends NOTHING (the diff is source code — review before publishing).
41
+ Also set via TRIBUNAL_ALLOW_SEND_DIFF=1.
42
+ --out <file> Write the claims block to a file (default: stdout).
43
+ --cwd <dir> Repo root (default: current directory).
44
+
45
+ The propose → check loop:
46
+ tribunal propose --diff pr.diff --allow-send-diff --out claims.md
47
+ tribunal check --diff pr.diff --pr-body claims.md
22
48
 
23
49
  Tribunal gates only on CONTRADICTED (a syntactic certainty). UNVERIFIED never blocks,
24
- and no LLM is in the verification path.
50
+ and no LLM is in the verification path. propose only suggests claims for check to verify.
51
+
52
+ -h, --help Show this help.
25
53
  `;
26
54
  function parseArgs(argv) {
27
- const opts = { command: '', cwd: process.cwd(), format: 'md', hardFail: false };
55
+ const opts = {
56
+ command: '',
57
+ cwd: process.cwd(),
58
+ format: 'md',
59
+ hardFail: false,
60
+ allowSendDiff: false,
61
+ noSkipGenerated: false,
62
+ };
28
63
  const rest = [...argv];
29
64
  while (rest.length) {
30
65
  const a = rest.shift();
@@ -46,22 +81,46 @@ function parseArgs(argv) {
46
81
  opts.claimsFile = rest.shift();
47
82
  break;
48
83
  case '--pr-body':
84
+ // In `propose`, --pr-body is context for the model; in `check`, it's a fenced claims source.
85
+ // We resolve which meaning applies at dispatch time; stash the path on both fields.
49
86
  opts.prBodyFile = rest.shift();
87
+ opts.proposePrBodyFile = opts.prBodyFile;
88
+ break;
89
+ case '--endpoint':
90
+ opts.endpoint = rest.shift();
91
+ break;
92
+ case '--model':
93
+ opts.model = rest.shift();
94
+ break;
95
+ case '--api-key':
96
+ opts.apiKey = rest.shift();
97
+ break;
98
+ case '--allow-send-diff':
99
+ opts.allowSendDiff = true;
100
+ break;
101
+ case '--out':
102
+ opts.outFile = rest.shift();
50
103
  break;
51
104
  case '--cwd':
52
105
  opts.cwd = rest.shift() ?? opts.cwd;
53
106
  break;
54
107
  case '--format': {
55
108
  const f = rest.shift();
56
- if (f === 'md' || f === 'json')
109
+ if (f === 'md' || f === 'json' || f === 'sarif')
57
110
  opts.format = f;
58
111
  else
59
- throw new Error(`Unknown format: ${f} (expected md or json)`);
112
+ throw new Error(`Unknown format: ${f} (expected md, json, or sarif)`);
60
113
  break;
61
114
  }
62
115
  case '--hard-fail':
63
116
  opts.hardFail = true;
64
117
  break;
118
+ case '--no-skip-generated':
119
+ opts.noSkipGenerated = true;
120
+ break;
121
+ case '--config':
122
+ opts.configFile = rest.shift();
123
+ break;
65
124
  default:
66
125
  if (!a.startsWith('-') && !opts.command)
67
126
  opts.command = a;
@@ -71,21 +130,70 @@ function parseArgs(argv) {
71
130
  }
72
131
  return opts;
73
132
  }
74
- function main() {
75
- let opts;
76
- try {
77
- opts = parseArgs(process.argv.slice(2));
133
+ /** Resolve the propose provider config from flags, then env, failing loudly if incomplete. */
134
+ function resolveProvider(opts) {
135
+ const endpoint = opts.endpoint ?? process.env.TRIBUNAL_ENDPOINT;
136
+ const model = opts.model ?? process.env.TRIBUNAL_MODEL;
137
+ const apiKey = opts.apiKey ?? process.env.TRIBUNAL_API_KEY;
138
+ if (!endpoint) {
139
+ throw new Error('propose: no endpoint. Pass --endpoint <url> or set TRIBUNAL_ENDPOINT ' +
140
+ '(an OpenAI-compatible chat-completions base URL).');
78
141
  }
79
- catch (err) {
80
- process.stderr.write(`${err.message}\n\n${HELP}`);
81
- process.exit(2);
142
+ if (!model) {
143
+ throw new Error('propose: no model. Pass --model <name> or set TRIBUNAL_MODEL.');
82
144
  }
83
- if (opts.command === 'help' || opts.command === '') {
84
- process.stdout.write(HELP);
85
- process.exit(0);
145
+ return { endpoint, model, apiKey };
146
+ }
147
+ async function runProposeCmd(opts) {
148
+ if (!opts.diffFile) {
149
+ throw new Error('propose: --diff <file> is required (the diff to propose claims for).');
86
150
  }
87
- if (opts.command !== 'check') {
88
- process.stderr.write(`Unknown command: ${opts.command}\n\n${HELP}`);
151
+ const diff = readFileSync(opts.diffFile, 'utf8');
152
+ const prBody = opts.proposePrBodyFile ? readFileSync(opts.proposePrBodyFile, 'utf8') : undefined;
153
+ const allowSendDiff = opts.allowSendDiff || process.env.TRIBUNAL_ALLOW_SEND_DIFF === '1' ||
154
+ process.env.TRIBUNAL_ALLOW_SEND_DIFF === 'true';
155
+ const provider = resolveProvider(opts);
156
+ const result = await runPropose({ diff, prBody, provider, allowSendDiff });
157
+ if (opts.outFile) {
158
+ writeFileSync(opts.outFile, `${result.block}\n`);
159
+ process.stderr.write(result.sent
160
+ ? `propose: wrote ${result.claims.length} candidate claim(s) to ${opts.outFile}\n`
161
+ : `propose: send-guard withheld the request; wrote an empty block to ${opts.outFile}\n`);
162
+ }
163
+ else if (result.sent) {
164
+ // When the prompt was already printed by the send-guard path, avoid duplicating it; only print the
165
+ // block when we actually sent and got a response.
166
+ process.stdout.write(`${result.block}\n`);
167
+ }
168
+ process.exit(0);
169
+ }
170
+ function runCheckCmd(opts) {
171
+ // riskyDiffNoTest skips generated/build-output paths by default. Disable via --no-skip-generated or
172
+ // TRIBUNAL_NO_SKIP_GENERATED=1 (an opinionated default must never silently suppress a wanted file).
173
+ if (opts.noSkipGenerated ||
174
+ process.env.TRIBUNAL_NO_SKIP_GENERATED === '1' ||
175
+ process.env.TRIBUNAL_NO_SKIP_GENERATED === 'true') {
176
+ setSkipGenerated(false);
177
+ }
178
+ // Load tribunal.yml (auto-discovered at repo root, or --config / TRIBUNAL_CONFIG). Absent = all
179
+ // defaults, no behavior change. Present-but-malformed fails loud. Applied: extra generated-paths are
180
+ // appended to the built-ins (extend-defaults); analyzers: false disables that analyzer.
181
+ let analyzerOverride;
182
+ try {
183
+ const config = loadConfig(opts.cwd, opts.configFile);
184
+ if (config) {
185
+ if (config.generatedPaths && config.generatedPaths.length) {
186
+ setExtraGeneratedPaths(config.generatedPaths);
187
+ }
188
+ if (config.analyzers) {
189
+ const disabled = Object.entries(config.analyzers).filter(([, v]) => v === false).map(([k]) => k);
190
+ if (disabled.length)
191
+ analyzerOverride = defaultAnalyzers.filter((a) => !disabled.includes(a.id));
192
+ }
193
+ }
194
+ }
195
+ catch (err) {
196
+ process.stderr.write(`tribunal: ${err.message}\n`);
89
197
  process.exit(2);
90
198
  }
91
199
  let claims;
@@ -102,15 +210,50 @@ function main() {
102
210
  };
103
211
  let report;
104
212
  try {
105
- report = runTribunal(src);
213
+ report = runTribunal(src, analyzerOverride ? { analyzers: analyzerOverride } : {});
106
214
  }
107
215
  catch (err) {
108
216
  process.stderr.write(`tribunal: ${err.message}\n`);
109
217
  process.exit(2);
110
218
  }
111
- const out = opts.format === 'json' ? renderJson(report) : renderMarkdown(report, opts.hardFail);
219
+ let out;
220
+ if (opts.format === 'json')
221
+ out = renderJson(report);
222
+ else if (opts.format === 'sarif')
223
+ out = renderSarif(report);
224
+ else
225
+ out = renderMarkdown(report, opts.hardFail);
112
226
  process.stdout.write(`${out}\n`);
113
227
  process.exit(exitCode(report, opts.hardFail));
114
228
  }
229
+ async function main() {
230
+ let opts;
231
+ try {
232
+ opts = parseArgs(process.argv.slice(2));
233
+ }
234
+ catch (err) {
235
+ process.stderr.write(`${err.message}\n\n${HELP}`);
236
+ process.exit(2);
237
+ }
238
+ if (opts.command === 'help' || opts.command === '') {
239
+ process.stdout.write(HELP);
240
+ process.exit(0);
241
+ }
242
+ if (opts.command === 'propose') {
243
+ try {
244
+ await runProposeCmd(opts);
245
+ }
246
+ catch (err) {
247
+ process.stderr.write(`tribunal: ${err.message}\n`);
248
+ process.exit(2);
249
+ }
250
+ return;
251
+ }
252
+ if (opts.command !== 'check') {
253
+ process.stderr.write(`Unknown command: ${opts.command}\n\n${HELP}`);
254
+ process.exit(2);
255
+ }
256
+ runCheckCmd(opts);
257
+ }
115
258
  main();
116
259
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { TribunalConfig } from './types.js';
2
+ export declare const CONFIG_FILENAME = "tribunal.yml";
3
+ /**
4
+ * Register the known analyzer ids (once, at CLI/program startup). loadConfig validates `analyzers:` keys
5
+ * against this set so a typo fails loudly instead of silently no-op'ing.
6
+ */
7
+ export declare function setKnownAnalyzerIds(ids: readonly string[]): void;
8
+ /**
9
+ * Load + validate a TribunalConfig.
10
+ *
11
+ * Resolution order for the file path:
12
+ * 1. an explicit `configPath` argument (the `--config` flag value)
13
+ * 2. the `TRIBUNAL_CONFIG` env var
14
+ * 3. `<repoRoot>/tribunal.yml` (auto-discovery)
15
+ *
16
+ * Returns `null` when no file is present (≡ all defaults). Throws on a present-but-malformed file or on
17
+ * an unknown analyzer id (fail-loud, never silently degrade).
18
+ */
19
+ export declare function loadConfig(repoRoot: string, configPath?: string): TribunalConfig | null;
@@ -0,0 +1,89 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { parseYamlSubset } from './parseYaml.js';
4
+ export const CONFIG_FILENAME = 'tribunal.yml';
5
+ /** The set of analyzer ids accepted under `analyzers:` — injected so loadConfig has no analyzer import cycle. */
6
+ let knownAnalyzerIds = [];
7
+ /**
8
+ * Register the known analyzer ids (once, at CLI/program startup). loadConfig validates `analyzers:` keys
9
+ * against this set so a typo fails loudly instead of silently no-op'ing.
10
+ */
11
+ export function setKnownAnalyzerIds(ids) {
12
+ knownAnalyzerIds = ids;
13
+ }
14
+ /**
15
+ * Load + validate a TribunalConfig.
16
+ *
17
+ * Resolution order for the file path:
18
+ * 1. an explicit `configPath` argument (the `--config` flag value)
19
+ * 2. the `TRIBUNAL_CONFIG` env var
20
+ * 3. `<repoRoot>/tribunal.yml` (auto-discovery)
21
+ *
22
+ * Returns `null` when no file is present (≡ all defaults). Throws on a present-but-malformed file or on
23
+ * an unknown analyzer id (fail-loud, never silently degrade).
24
+ */
25
+ export function loadConfig(repoRoot, configPath) {
26
+ const explicit = configPath ?? process.env.TRIBUNAL_CONFIG;
27
+ const file = explicit ? resolve(explicit) : join(repoRoot, CONFIG_FILENAME);
28
+ if (!existsSync(file))
29
+ return null;
30
+ let raw;
31
+ try {
32
+ raw = readFileSync(file, 'utf8');
33
+ }
34
+ catch (err) {
35
+ throw new Error(`tribunal.yml: could not read ${file}: ${err.message}`);
36
+ }
37
+ let parsed;
38
+ try {
39
+ parsed = parseYamlSubset(raw);
40
+ }
41
+ catch (err) {
42
+ throw new Error(`${err.message} (in ${file})`);
43
+ }
44
+ const config = {};
45
+ // analyzers: map of id → boolean, validated against the known registry.
46
+ if ('analyzers' in parsed && parsed.analyzers != null) {
47
+ const a = parsed.analyzers;
48
+ if (typeof a !== 'object' || Array.isArray(a)) {
49
+ throw new Error(`tribunal.yml: 'analyzers' must be a map of id: true|false (in ${file})`);
50
+ }
51
+ const known = new Set(knownAnalyzerIds);
52
+ if (known.size === 0) {
53
+ // Defensive: caller forgot to register ids. Allow through without validation rather than block.
54
+ }
55
+ const out = {};
56
+ for (const [k, v] of Object.entries(a)) {
57
+ if (known.size > 0 && !known.has(k)) {
58
+ throw new Error(`tribunal.yml: unknown analyzer '${k}'. Known: ${[...known].sort().join(', ')} (in ${file})`);
59
+ }
60
+ if (typeof v !== 'boolean') {
61
+ throw new Error(`tribunal.yml: analyzers.${k} must be true|false, got ${JSON.stringify(v)} (in ${file})`);
62
+ }
63
+ out[k] = v;
64
+ }
65
+ config.analyzers = out;
66
+ }
67
+ // generated-paths: list of strings, appended to built-ins at match time.
68
+ if ('generated-paths' in parsed && parsed['generated-paths'] != null) {
69
+ const gp = parsed['generated-paths'];
70
+ if (!Array.isArray(gp)) {
71
+ throw new Error(`tribunal.yml: 'generated-paths' must be a list (in ${file})`);
72
+ }
73
+ config.generatedPaths = gp.map((item, idx) => {
74
+ if (typeof item !== 'string') {
75
+ throw new Error(`tribunal.yml: generated-paths[${idx}] must be a string (in ${file})`);
76
+ }
77
+ return item;
78
+ });
79
+ }
80
+ // Reject unknown top-level keys (fail-loud — typos shouldn't silently no-op).
81
+ const knownKeys = new Set(['analyzers', 'generated-paths']);
82
+ for (const k of Object.keys(parsed)) {
83
+ if (!knownKeys.has(k)) {
84
+ throw new Error(`tribunal.yml: unknown key '${k}'. Supported: ${[...knownKeys].join(', ')} (in ${file})`);
85
+ }
86
+ }
87
+ return config;
88
+ }
89
+ //# sourceMappingURL=loadConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadConfig.js","sourceRoot":"","sources":["../../src/config/loadConfig.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A tiny, zero-dependency parser for the narrow YAML *subset* that tribunal.yml uses.
3
+ *
4
+ * This is deliberately NOT a general YAML parser — it covers exactly what the config schema needs and
5
+ * rejects anything else with a clear error, so a malformed config never silently degrades to defaults:
6
+ *
7
+ * - top-level `key: value` (value = scalar: string | boolean | number)
8
+ * - inline list value: `key: [a, b, "c"]`
9
+ * - block list: a `key:` line followed by ` - item` lines (two-space indent)
10
+ * - one level of nesting: a `key:` line followed by ` child: value` lines (the `analyzers:` map)
11
+ * - `#` line comments and trailing comments, blank lines
12
+ *
13
+ * No anchors, no multiline strings, no flow mappings, no deeper nesting. If the schema grows, either
14
+ * extend this or swap in a real parser (js-yaml) — but for now keeping zero runtime deps is a project
15
+ * value, and this subset is fully covered by unit tests.
16
+ */
17
+ export declare function parseYamlSubset(text: string): Record<string, unknown>;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * A tiny, zero-dependency parser for the narrow YAML *subset* that tribunal.yml uses.
3
+ *
4
+ * This is deliberately NOT a general YAML parser — it covers exactly what the config schema needs and
5
+ * rejects anything else with a clear error, so a malformed config never silently degrades to defaults:
6
+ *
7
+ * - top-level `key: value` (value = scalar: string | boolean | number)
8
+ * - inline list value: `key: [a, b, "c"]`
9
+ * - block list: a `key:` line followed by ` - item` lines (two-space indent)
10
+ * - one level of nesting: a `key:` line followed by ` child: value` lines (the `analyzers:` map)
11
+ * - `#` line comments and trailing comments, blank lines
12
+ *
13
+ * No anchors, no multiline strings, no flow mappings, no deeper nesting. If the schema grows, either
14
+ * extend this or swap in a real parser (js-yaml) — but for now keeping zero runtime deps is a project
15
+ * value, and this subset is fully covered by unit tests.
16
+ */
17
+ /** Parse a scalar token (after stripping comments) into string | boolean | number. */
18
+ function parseScalar(raw) {
19
+ const s = raw.trim();
20
+ if (s === '')
21
+ return '';
22
+ // strip surrounding quotes (single or double) → always a string
23
+ if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
24
+ return s.slice(1, -1);
25
+ }
26
+ if (s === 'true' || s === 'True' || s === 'TRUE')
27
+ return true;
28
+ if (s === 'false' || s === 'False' || s === 'FALSE')
29
+ return false;
30
+ // integer/float only — reject hex/leading-zero quirks by deferring to Number
31
+ if (/^-?\d+$/.test(s))
32
+ return parseInt(s, 10);
33
+ if (/^-?\d+\.\d+$/.test(s))
34
+ return parseFloat(s);
35
+ return s; // bare string
36
+ }
37
+ /** Parse an inline flow list `[a, b, "c"]` into a (string|number|boolean)[]. */
38
+ function parseInlineList(raw) {
39
+ const inner = raw.trim();
40
+ if (!inner.startsWith('[') || !inner.endsWith(']')) {
41
+ throw new Error(`Expected an inline list '[...]', got: ${raw}`);
42
+ }
43
+ const body = inner.slice(1, -1).trim();
44
+ if (body === '')
45
+ return [];
46
+ return body.split(',').map((part) => parseScalar(part));
47
+ }
48
+ /**
49
+ * Strip a trailing `# comment` from a value-bearing line. A `#` only counts as a comment start when
50
+ * preceded by whitespace or at line start (so `#fff` in a value is preserved, ` # note` is stripped).
51
+ */
52
+ function stripTrailingComment(line) {
53
+ // naive but sufficient for this subset: a `#` that has a space before it (or is at col 0) is a comment.
54
+ const hash = line.search(/\s#|\s$|^#/);
55
+ if (hash === -1)
56
+ return line;
57
+ if (line[hash] === '#')
58
+ return ''; // whole line is a comment
59
+ return line.slice(0, hash);
60
+ }
61
+ export function parseYamlSubset(text) {
62
+ const result = {};
63
+ const lines = text.split('\n');
64
+ // State for the current top-level key we're filling (a list or nested map).
65
+ let listKey = null;
66
+ let mapKey = null;
67
+ const finishBlock = () => {
68
+ listKey = null;
69
+ mapKey = null;
70
+ };
71
+ for (let i = 0; i < lines.length; i++) {
72
+ const original = lines[i];
73
+ const stripped = stripTrailingComment(original);
74
+ // Skip blank / full-comment lines.
75
+ if (stripped.trim() === '' || stripped.trim().startsWith('#'))
76
+ continue;
77
+ const indent = stripped.length - stripped.trimStart().length;
78
+ // Indented block item under the current list key: ` - value`.
79
+ if (listKey && /^-\s+/.test(stripped.trim())) {
80
+ const item = parseScalar(stripped.trim().replace(/^-\s+/, ''));
81
+ result[listKey].push(item);
82
+ continue;
83
+ }
84
+ // Indented child under the current map key: ` child: value`.
85
+ if (mapKey && indent >= 2 && /^[\w-]+:\s*/.test(stripped.trim())) {
86
+ const m = stripped.trim().match(/^([\w-]+):\s*(.*)$/);
87
+ if (!m)
88
+ throw new Error(`tribunal.yml:${i + 1}: malformed map entry: ${original.trim()}`);
89
+ const [, childKey, childValRaw] = m;
90
+ if (childValRaw.trim() === '') {
91
+ throw new Error(`tribunal.yml:${i + 1}: nested map under '${mapKey}.${childKey}' is not supported`);
92
+ }
93
+ result[mapKey][childKey] = parseScalar(childValRaw);
94
+ continue;
95
+ }
96
+ // Otherwise: a top-level `key: value` or `key:` (opening a block). Must be at indent 0.
97
+ if (indent !== 0) {
98
+ throw new Error(`tribunal.yml:${i + 1}: unexpected indentation (no open block): ${original.trim()}`);
99
+ }
100
+ finishBlock();
101
+ const m = stripped.trim().match(/^([\w-]+):\s*(.*)$/);
102
+ if (!m)
103
+ throw new Error(`tribunal.yml:${i + 1}: expected 'key: value', got: ${original.trim()}`);
104
+ const [, key, valRaw] = m;
105
+ const val = valRaw.trim();
106
+ if (val === '') {
107
+ // Opens a block — we don't yet know if it's a list or a map; decide on the next non-blank line.
108
+ // For our schema, `analyzers:` is a map and `generated-paths:` is a list. Peek ahead.
109
+ const nextNonBlank = lines
110
+ .slice(i + 1)
111
+ .find((l) => stripTrailingComment(l).trim() !== '' && !stripTrailingComment(l).trim().startsWith('#'));
112
+ if (nextNonBlank && /^-\s+/.test(nextNonBlank.trim())) {
113
+ result[key] = [];
114
+ listKey = key;
115
+ }
116
+ else {
117
+ result[key] = {};
118
+ mapKey = key;
119
+ }
120
+ continue;
121
+ }
122
+ if (val.startsWith('[')) {
123
+ result[key] = parseInlineList(val);
124
+ continue;
125
+ }
126
+ result[key] = parseScalar(val);
127
+ }
128
+ finishBlock();
129
+ return result;
130
+ }
131
+ //# sourceMappingURL=parseYaml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseYaml.js","sourceRoot":"","sources":["../../src/config/parseYaml.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The tribunal.yml config shape. All fields optional — an absent file means "all defaults" and is
3
+ * indistinguishable from no config (config is purely additive).
4
+ */
5
+ export interface TribunalConfig {
6
+ /**
7
+ * Per-analyzer enable/disable, keyed by analyzer id. Default: every analyzer enabled. Unknown ids are
8
+ * rejected at load time (typo guard), so a misspelled key fails loudly rather than silently no-op'ing.
9
+ */
10
+ analyzers?: Record<string, boolean>;
11
+ /**
12
+ * EXTRA generated/build-output paths, appended to the built-in defaults. The built-ins (`dist/`,
13
+ * `action-dist/`, …) are always present — config can only ADD coverage, never drop a safety net.
14
+ * See src/paths.ts for the defaults and src/config/loadConfig.ts for merge semantics.
15
+ */
16
+ generatedPaths?: string[];
17
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":""}
@@ -5,5 +5,9 @@ import type { ChangedFile } from '../types.js';
5
5
  *
6
6
  * We deliberately track only new-file line numbers: analyzers reason about the post-change tree, so a
7
7
  * "touched" node is one whose new-file line range overlaps `addedLines`.
8
+ *
9
+ * Both git-style diffs (`diff --git`) and plain unified diffs (no `diff --git` header, e.g. `diff -u` or
10
+ * a `git format-patch` body) are supported. A plain diff opens a file record on its first `---`/`+++`
11
+ * header so a hand-written or piped patch is never silently dropped to `[]`.
8
12
  */
9
13
  export declare function parseUnifiedDiff(diff: string): ChangedFile[];