patdown 0.4.0 → 0.5.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 (43) hide show
  1. package/README.md +46 -11
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +45 -20
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +4 -1
  7. package/dist/patdown-console-rule-blocks.d.ts +13 -0
  8. package/dist/patdown-console-rule-blocks.d.ts.map +1 -0
  9. package/dist/patdown-console-rule-blocks.js +80 -0
  10. package/dist/patdown-evidence-regions.d.ts +44 -0
  11. package/dist/patdown-evidence-regions.d.ts.map +1 -0
  12. package/dist/patdown-evidence-regions.js +124 -0
  13. package/dist/patdown-github-actions-env.d.ts +6 -0
  14. package/dist/patdown-github-actions-env.d.ts.map +1 -0
  15. package/dist/patdown-github-actions-env.js +17 -0
  16. package/dist/patdown-github-actions-output.d.ts +8 -0
  17. package/dist/patdown-github-actions-output.d.ts.map +1 -0
  18. package/dist/patdown-github-actions-output.js +52 -0
  19. package/dist/patdown-github-actions-summary.d.ts +19 -0
  20. package/dist/patdown-github-actions-summary.d.ts.map +1 -0
  21. package/dist/patdown-github-actions-summary.js +171 -0
  22. package/dist/patdown-glob.d.ts +9 -0
  23. package/dist/patdown-glob.d.ts.map +1 -0
  24. package/dist/patdown-glob.js +21 -0
  25. package/dist/patdown-judge.d.ts +25 -0
  26. package/dist/patdown-judge.d.ts.map +1 -1
  27. package/dist/patdown-judge.js +23 -0
  28. package/dist/patdown-lint-files.d.ts +14 -0
  29. package/dist/patdown-lint-files.d.ts.map +1 -0
  30. package/dist/patdown-lint-files.js +75 -0
  31. package/dist/patdown-lint.d.ts +2 -1
  32. package/dist/patdown-lint.d.ts.map +1 -1
  33. package/dist/patdown-lint.js +57 -21
  34. package/dist/patdown-output.d.ts +22 -4
  35. package/dist/patdown-output.d.ts.map +1 -1
  36. package/dist/patdown-output.js +79 -14
  37. package/dist/run-patdown-cli.d.ts +3 -1
  38. package/dist/run-patdown-cli.d.ts.map +1 -1
  39. package/dist/run-patdown-cli.js +16 -3
  40. package/dist/typesafe-judge.d.ts +1 -0
  41. package/dist/typesafe-judge.d.ts.map +1 -1
  42. package/dist/typesafe-judge.js +15 -1
  43. package/package.json +5 -5
package/README.md CHANGED
@@ -19,22 +19,29 @@ Node.js **>=22.22.2** and pnpm **11.8.0**. CI runs checks and built-CLI smoke te
19
19
 
20
20
  ## Commands
21
21
 
22
+ From this repo, prefer the workspace script. It builds `patdown` through Turborepo (`dependsOn: build`) and then runs the CLI from your current directory:
23
+
22
24
  ```
23
25
  pnpm -w patdown
24
26
  pnpm -w patdown -- --rules ./rules.md
27
+ pnpm -w patdown -- --rules ./packs/typescript --verbose
25
28
  pnpm -w patdown -- rules
26
29
  pnpm -w patdown -- ask "Is this markdown heading title case?" --input-text "# Hello World"
27
30
  pnpm -w patdown -- ask "Is this urgent?" --input-text "ASAP" --verbose
28
31
  pnpm -w patdown -- --yes-threshold 0.9
32
+ pnpm -w patdown -- --files src/cli.ts --files README.md
33
+ pnpm -w patdown -- --files-from changed.txt --verbose
29
34
  ```
30
35
 
36
+ Do not run `pnpm build` by hand just to exercise the CLI, and do not use bare `npx patdown` against an unpublished branch (that installs the last registry version). After a release, consumers use `npx patdown` / `pnpm add -D patdown` as usual.
37
+
31
38
  Default command lints from the current directory. `rules` prints what it loaded. `ask` answers a yes/no question, no files involved. It prints `yes` or `no`; `--verbose` also shows a P(yes) shade bar, the cutoff, and how long the judge call took. The old `--noul` and `--state` flags have been replaced by a positional question and `--input-text`.
32
39
 
33
- To judge piped output, use `--stdin`. For example, after building with `pnpm -w build` or running any `pnpm -w patdown` command:
40
+ To judge piped output, use `--stdin`:
34
41
 
35
42
  ```sh
36
- git diff --cached | node apps/patdown/dist/patdown-cli-bin.js ask "Does this diff introduce debugging statements?" --stdin
37
- printf 'ASAP: production is down\n' | node apps/patdown/dist/patdown-cli-bin.js ask "Is this urgent?" --stdin
43
+ git diff --cached | pnpm -w patdown -- ask "Does this diff introduce debugging statements?" --stdin
44
+ printf 'ASAP: production is down\n' | pnpm -w patdown -- ask "Is this urgent?" --stdin
38
45
  ```
39
46
 
40
47
  These send the piped content to the configured judge. Do not pipe secrets. `--stdin` reads UTF-8 text through EOF, preserving newlines; it cannot be combined with `--input-text`. Without either option, the input is an empty string. `ask` reports yes/no without treating yes as a failing exit status.
@@ -94,6 +101,22 @@ await Effect.runPromise(runPatdownCli(PatdownRuleSourceLive))
94
101
 
95
102
  See [the adapter guide](docs/rule-source-adapters.md) for the interface, a multi-file parser, resolution rules, resource lifetimes, and local development setup. Adapters execute trusted local code. Install the CLI from npm as `patdown`; adapters import `@patdown/rules` and may import `patdown` for types.
96
103
 
104
+ ## Packs
105
+
106
+ Optional rule bundles live under [`packs/`](packs/README.md). The CLI only runs rules; packs are content you can take all of, some of, or skip:
107
+
108
+ - [`packs/typescript`](packs/typescript/) — type-safety crimes and type laundering
109
+ - [`packs/effect`](packs/effect/) — Effect v4 services, schemas, errors, time, HTTP, tests
110
+ - [`packs/anti-slop`](packs/anti-slop/) — fuzzy counterpart to [Dillon Mulroy's anti-slop](https://github.com/dmmulroy/anti-slop)
111
+
112
+ ```sh
113
+ # whole pack, or a single rule file from a pack
114
+ patdown --rules ./packs/effect --files-from changed.txt --verbose
115
+ patdown --rules ./packs/typescript/do-not-launder-types-with-casts.md
116
+ ```
117
+
118
+ `--rules` accepts a markdown file or a directory of rule files (every `*.md` except `README.md`). Prefer stacked, backtick-wrapped `globs:` lines; commas inside one `globs:` value are separators.
119
+
97
120
  ## Rules
98
121
 
99
122
  One `# heading` per rule. Optional `globs:` and `yes-threshold:` lines sit immediately under the heading, in either order. Commas or spaces, extra `globs:` lines stack. A second `yes-threshold:` line is an error. Text above the first heading is ignored. Headings inside fenced code are ignored. Only `#` headings start rules; `##` and deeper headings stay in the rule body, so sections like `## Not allowed` and `## Exceptions` are fine.
@@ -119,12 +142,22 @@ FAIL README.md: No title case
119
142
  patdown: failed
120
143
  ```
121
144
 
122
- Exit 1 on a violation, a missing rules file, a read error, an invalid cutoff, or a judge error. Add `--verbose` to show a P(yes) shade bar, the numeric probability, the cutoff, and elapsed judge time. Lint totals that time across every rule/file pair.
145
+ Exit 1 on a violation, a missing rules file, a read error, an invalid cutoff, or a judge error. Add `--verbose` to group local lint output into per-rule console blocks (file, shade bar, P(yes), elapsed). Quiet mode stays one `PASS`/`FAIL` line per judgment. Lint totals elapsed time across every rule/file pair.
146
+
147
+ ```
148
+ ┌ Do not launder types with casts ───────────────────────── 1✗ / 1
149
+
150
+ ├─ apps/foo
151
+
152
+ │ ✗ ▓▓▓▓▓▓▓▓▒░ 0.86 312ms service.ts
153
+
123
154
 
124
- ````
125
- FAIL service.ts: explicit-actor (▓▓▓▓▓▓▓▓▒░ estimated P(yes): 0.86; cutoff: >0.85; elapsed: 312ms)
126
155
  patdown: failed (elapsed: 1840ms)
127
- ``` Patdown counts estimated P(yes) strictly above the cutoff as yes; for lint, yes means violation. Default cutoff is 0.85. Override it with `--yes-threshold`, package.json `patdown.yesThreshold`, or a per-rule `yes-threshold:` line. The flag wins over package.json; a per-rule value wins for that rule only. `1` is rejected because nothing can exceed it. This cutoff belongs to patdown, not the provider.
156
+ ```
157
+
158
+ `--files` (repeatable) and `--files-from` restrict lint to an explicit path list intersected with each rule's globs. That is how CI should run over a pull request. See [GitHub Actions](docs/github-actions.md).
159
+
160
+ Patdown counts estimated P(yes) strictly above the cutoff as yes; for lint, yes means violation. Default cutoff is 0.85. Override it with `--yes-threshold`, package.json `patdown.yesThreshold`, or a per-rule `yes-threshold:` line. The flag wins over package.json; a per-rule value wins for that rule only. `1` is rejected because nothing can exceed it. This cutoff belongs to patdown, not the provider.
128
161
 
129
162
  See [judge providers](docs/judge-providers.md) for custom layers and the planned Effect Decision integration.
130
163
 
@@ -155,8 +188,8 @@ const JsonOutputLive = Layer.succeed(PatdownOutput, {
155
188
  writeLintResult: (result, _verbose) => Console.log(JSON.stringify(result)),
156
189
  writeRulesDocument: (document) => Console.log(JSON.stringify(document)),
157
190
  writeNoFilesMatched: (ruleTitle) => Console.log(JSON.stringify({ skipped: ruleTitle })),
158
- writeLintOk: Console.log(JSON.stringify({ status: 'ok' })),
159
- writeLintFailed: Console.log(JSON.stringify({ status: 'failed' })),
191
+ writeLintOk: () => Console.log(JSON.stringify({ status: 'ok' })),
192
+ writeLintFailed: () => Console.log(JSON.stringify({ status: 'failed' })),
160
193
  })
161
194
 
162
195
  await Effect.runPromise(
@@ -167,7 +200,7 @@ await Effect.runPromise(
167
200
  JsonOutputLive,
168
201
  ),
169
202
  )
170
- ````
203
+ ```
171
204
 
172
205
  The output service receives structured judgments and lint results, including probabilities even when `--verbose` is off. Your layer decides what to print, collect, or omit. Formatting does not change the cutoff or exit status. The example emits one JSON object per output event, not a single JSON document for the entire run.
173
206
 
@@ -177,7 +210,7 @@ This is an embedding API, not a `--format` flag or dynamically discovered output
177
210
 
178
211
  With the default TypeSafe backend, `TYPESAFE_API_KEY` is required for lint and `ask`. Optional `TYPESAFE_BASE_URL` (default `https://api.typesafe.ai`) and `TYPESAFE_DEFAULT_MODEL` (default `jev-latest`).
179
212
 
180
- `pnpm -w patdown` forwards `TYPESAFE_*` through Turbo.
213
+ `pnpm -w patdown` inherits `TYPESAFE_*` from your environment.
181
214
 
182
215
  ## Release
183
216
 
@@ -203,6 +236,8 @@ Inspired by [pi-warden](https://github.com/DevMortimer/pi-warden). Same idea, in
203
236
 
204
237
  [Jev Review](https://github.com/devagrawal09/jev-review) is a Jev-backed diff/codebase reviewer with a local dashboard of those judgments. Patdown stays in the terminal; `--verbose` draws a P(yes) shade bar instead of a GUI.
205
238
 
239
+ [Dillon Mulroy's anti-slop](https://github.com/dmmulroy/anti-slop) is the Oxlint ruleset that [`packs/anti-slop`](packs/anti-slop/) rephrases for fuzzy judging. Keep the static rules for exact AST hits; use the pack for paraphrases and type-laundering that still looks clean to a linter.
240
+
206
241
  Name inspired by It's Always Sunny in Philadelphia
207
242
 
208
243
  <img width="511" height="415" alt="image" src="https://github.com/user-attachments/assets/f7c73138-3914-4fbd-9e6b-7a37d161334a" />
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EAKjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAU,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACxD,OAAO,EAAY,OAAO,EAAQ,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,YAAY,EAAuC,MAAM,oBAAoB,CAAA;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA8BnD,KAAK,mBAAmB,GACrB,UAAU,CAAC,UAAU,GACrB,KAAK,CAAC,KAAK,GACX,YAAY,GACZ,IAAI,CAAC,IAAI,GACT,aAAa,GACb,iBAAiB,CAAA;AAuBpB,4EAA4E;AAC5E,wBAAgB,kBAAkB,CACjC,gBAAgB,GAAE,OAAc,GAC9B,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,CAAC,CA6GvE;AAED,uEAAuE;AACvE,eAAO,MAAM,cAAc,uEAAuB,CAAA"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,iBAAiB,EAKjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAU,UAAU,EAAU,IAAI,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAChE,OAAO,EAAY,OAAO,EAAQ,MAAM,qBAAqB,CAAA;AAE7D,OAAO,EAAE,YAAY,EAAuC,MAAM,oBAAoB,CAAA;AAGtF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA8CnD,KAAK,mBAAmB,GACrB,UAAU,CAAC,UAAU,GACrB,KAAK,CAAC,KAAK,GACX,YAAY,GACZ,IAAI,CAAC,IAAI,GACT,aAAa,GACb,iBAAiB,CAAA;AA+IpB,4EAA4E;AAC5E,wBAAgB,kBAAkB,CACjC,gBAAgB,GAAE,OAAc,GAC9B,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,CAAC,CA6BvE;AAED,uEAAuE;AACvE,eAAO,MAAM,cAAc,uEAAuB,CAAA"}
package/dist/cli.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { PatdownRuleSource, PatdownRulesFileMissing, PatdownRulesReadFailed, PatdownRulesLoadFailed, PatdownYesThresholdInvalid, } from '@patdown/rules';
2
- import { Effect, FileSystem, Path, Stdio } from 'effect';
2
+ import { Effect, FileSystem, Option, Path, Stdio } from 'effect';
3
3
  import { Argument, Command, Flag } from 'effect/unstable/cli';
4
4
  import { PatdownJudge, PatdownJudgeFailed, askPatdownJudge } from '#src/patdown-judge';
5
5
  import { runPatdownLint } from '#src/patdown-lint';
6
+ import { resolvePatdownLintFileSelection } from '#src/patdown-lint-files';
6
7
  import { PatdownOutput } from '#src/patdown-output';
7
8
  import { readPatdownQuestionInput } from '#src/patdown-question-input';
8
9
  import { loadConfiguredPatdownRules } from '#src/patdown-rule-source-adapter';
@@ -11,10 +12,13 @@ const failPatdown = (message) => Effect.sync(() => {
11
12
  process.exitCode = 1;
12
13
  process.stderr.write(`${message}\n`);
13
14
  });
14
- const rulesFileFlag = Flag.optional(Flag.string('rules')).pipe(Flag.withDescription('Path passed to the rule source. Markdown skips the AGENTS.PATDOWN.md walk'));
15
+ const rulesFileFlag = Flag.optional(Flag.string('rules')).pipe(Flag.withDescription('Rules markdown file or directory of rule files. Skips the AGENTS.PATDOWN.md walk'));
15
16
  const verboseFlag = Flag.boolean('verbose').pipe(Flag.withDefault(false), Flag.withDescription('Show estimated yes probabilities, the decision cutoff, and elapsed judge time'));
16
- const adapterFlag = Flag.optional(Flag.string('adapter')).pipe(Flag.withDescription('Module exporting PatdownRuleSourceLive, replacingkdown rule parsing'));
17
+ const adapterFlag = Flag.optional(Flag.string('adapter')).pipe(Flag.withDescription('Module exporting PatdownRuleSourceLive, replacing markdown rule parsing'));
17
18
  const yesThresholdFlag = Flag.optional(Flag.float('yes-threshold')).pipe(Flag.withDescription('Minimum exclusive P(yes) for yes; default 0.85, overridable per rule'));
19
+ const filesFlag = Flag.string('files').pipe(Flag.between(0, 10_000), Flag.withDescription('Restrict lint to these paths; intersects each rule glob'));
20
+ const filesFromFlag = Flag.optional(Flag.string('files-from')).pipe(Flag.withDescription('Newline-separated paths to lint; intersects each rule glob'));
21
+ const noGitHubFlag = Flag.boolean('no-github').pipe(Flag.withDefault(false), Flag.withDescription('Disable GitHub Actions summary and annotations'));
18
22
  function finishPatdownLint(failed, verbose, elapsedMs) {
19
23
  return Effect.gen(function* () {
20
24
  const output = yield* PatdownOutput;
@@ -28,9 +32,8 @@ function finishPatdownLint(failed, verbose, elapsedMs) {
28
32
  yield* output.writeLintOk(verbose ? elapsedMs : undefined);
29
33
  });
30
34
  }
31
- /** Builds commands with optional adapter discovery for embedded callers. */
32
- export function makePatdownCommand(discoverAdapters = true) {
33
- const rulesCommand = Command.make('rules', { adapter: adapterFlag, rules: rulesFileFlag }, ({ rules, adapter, }) => Effect.gen(function* () {
35
+ function makePatdownRulesCommand(discoverAdapters) {
36
+ return Command.make('rules', { adapter: adapterFlag, rules: rulesFileFlag }, ({ rules, adapter, }) => Effect.gen(function* () {
34
37
  const patdownRuleSource = yield* PatdownRuleSource;
35
38
  const output = yield* PatdownOutput;
36
39
  const document = yield* discoverAdapters
@@ -43,7 +46,9 @@ export function makePatdownCommand(discoverAdapters = true) {
43
46
  PatdownRulesLoadFailed: (error) => failPatdown(error.message),
44
47
  PatdownYesThresholdInvalid: (error) => failPatdown(error.message),
45
48
  }))).pipe(Command.withDescription('Load and print patdown rules'));
46
- const askCommand = Command.make('ask', {
49
+ }
50
+ function makePatdownAskCommand() {
51
+ return Command.make('ask', {
47
52
  question: Argument.string('question'),
48
53
  verbose: verboseFlag,
49
54
  yesThreshold: yesThresholdFlag,
@@ -59,27 +64,47 @@ export function makePatdownCommand(discoverAdapters = true) {
59
64
  PatdownJudgeFailed: (error) => failPatdown(error.message),
60
65
  PatdownYesThresholdInvalid: (error) => failPatdown(error.message),
61
66
  }))).pipe(Command.withDescription('Ask a yes/no question about text'));
62
- /** Root Effect CLI command for patdown. Default action lints files against AGENTS.PATDOWN.md. */
63
- return Command.make('patdown', {
64
- adapter: adapterFlag,
65
- rules: rulesFileFlag,
66
- verbose: verboseFlag,
67
- yesThreshold: yesThresholdFlag,
68
- }, ({ rules, adapter, verbose, yesThreshold }) => Effect.gen(function* () {
67
+ }
68
+ function runPatdownRootLint(discoverAdapters, options) {
69
+ return Effect.gen(function* () {
69
70
  const patdownRuleSource = yield* PatdownRuleSource;
70
- const cutoff = yield* resolvePatdownYesThreshold(yesThreshold);
71
+ const path = yield* Path.Path;
72
+ const cutoff = yield* resolvePatdownYesThreshold(options.yesThreshold);
73
+ const cwd = path.resolve('.');
74
+ const selection = yield* resolvePatdownLintFileSelection(cwd, options.files, options.filesFrom);
71
75
  const document = yield* discoverAdapters
72
- ? loadConfiguredPatdownRules(adapter, rules)
73
- : patdownRuleSource.loadPatdownRules(rules);
74
- const linted = yield* runPatdownLint(document, verbose, cutoff);
75
- yield* finishPatdownLint(linted.failed, verbose, linted.elapsedMs);
76
+ ? loadConfiguredPatdownRules(options.adapter, options.rules)
77
+ : patdownRuleSource.loadPatdownRules(options.rules);
78
+ const linted = yield* runPatdownLint(document, options.verbose, cutoff, selection);
79
+ yield* finishPatdownLint(linted.failed, options.verbose, linted.elapsedMs);
76
80
  }).pipe(Effect.catchTags({
77
81
  PatdownRulesLoadFailed: (error) => failPatdown(error.message),
78
82
  PatdownJudgeFailed: (error) => failPatdown(error.message),
79
83
  PatdownYesThresholdInvalid: (error) => failPatdown(error.message),
80
84
  PatdownRulesFileMissing: (error) => failPatdown(error.message),
81
85
  PatdownRulesReadFailed: (error) => failPatdown(error.message),
82
- }))).pipe(Command.withDescription('Lint a tree against fuzzykdown rules'), Command.withShortDescription('Patdown CLI'), Command.withSubcommands([askCommand, rulesCommand]));
86
+ }));
87
+ }
88
+ /** Builds commands with optional adapter discovery for embedded callers. */
89
+ export function makePatdownCommand(discoverAdapters = true) {
90
+ const rulesCommand = makePatdownRulesCommand(discoverAdapters);
91
+ const askCommand = makePatdownAskCommand();
92
+ return Command.make('patdown', {
93
+ adapter: adapterFlag,
94
+ files: filesFlag,
95
+ filesFrom: filesFromFlag,
96
+ noGitHub: noGitHubFlag,
97
+ rules: rulesFileFlag,
98
+ verbose: verboseFlag,
99
+ yesThreshold: yesThresholdFlag,
100
+ }, ({ rules, adapter, verbose, yesThreshold, files, filesFrom, noGitHub: _noGitHub }) => runPatdownRootLint(discoverAdapters, {
101
+ rules,
102
+ adapter,
103
+ verbose,
104
+ yesThreshold,
105
+ files,
106
+ filesFrom,
107
+ })).pipe(Command.withDescription('Lint a tree against fuzzy markdown rules'), Command.withShortDescription('Patdown CLI'), Command.withSubcommands([askCommand, rulesCommand]));
83
108
  }
84
109
  /** Default commands use explicit or package.json adapter discovery. */
85
110
  export const patdownCommand = makePatdownCommand();
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  export { patdownCommand, makePatdownCommand } from '#src/cli';
2
2
  export { PatdownOutput, PatdownOutputLive, type PatdownLintResult } from '#src/patdown-output';
3
+ export { PatdownGitHubActionsOutputLive } from '#src/patdown-github-actions-output';
4
+ export { formatPatdownGitHubActionsAnnotations, formatPatdownGitHubActionsSummary, formatPatdownRuleGuidance, patdownLintResultIsNearMiss, } from '#src/patdown-github-actions-summary';
5
+ export { patdownGitHubActionsIsEnabled, readPatdownGitHubStepSummaryPath, } from '#src/patdown-github-actions-env';
6
+ export type { PatdownLintFileSelection } from '#src/patdown-lint-files';
3
7
  export type { PatdownRuleSourceLayer } from '#src/patdown-rule-source-adapter';
4
8
  export { runPatdownCli } from '#src/run-patdown-cli';
5
- export { PatdownJudge, PatdownJudgeFailed, PatdownJudgmentSchema, askPatdownJudge, patdownJudgmentIsYes, patdownYesThreshold, type PatdownJudgment, type PatdownTimedJudgment, } from '#src/patdown-judge';
9
+ export { PatdownJudge, PatdownJudgeFailed, PatdownJudgmentSchema, askPatdownJudge, locatePatdownEvidence, patdownJudgmentIsYes, patdownYesThreshold, type PatdownEvidenceChoice, type PatdownEvidenceLocation, type PatdownJudgment, type PatdownTimedJudgment, } from '#src/patdown-judge';
6
10
  export { defaultPatdownYesThreshold, decodePatdownYesThreshold, PatdownYesThresholdInvalid, type PatdownYesThreshold, } from '@patdown/rules';
7
11
  export { TypeSafeJudgeLive } from '#src/typesafe-judge';
8
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE9F,YAAY,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GACzB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACN,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,mBAAmB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAE7D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE9F,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAA;AAEnF,OAAO,EACN,qCAAqC,EACrC,iCAAiC,EACjC,yBAAyB,EACzB,2BAA2B,GAC3B,MAAM,qCAAqC,CAAA;AAE5C,OAAO,EACN,6BAA6B,EAC7B,gCAAgC,GAChC,MAAM,iCAAiC,CAAA;AAExC,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAEvE,YAAY,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,oBAAoB,GACzB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACN,0BAA0B,EAC1B,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,mBAAmB,GACxB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  export { patdownCommand, makePatdownCommand } from '#src/cli';
2
2
  export { PatdownOutput, PatdownOutputLive } from '#src/patdown-output';
3
+ export { PatdownGitHubActionsOutputLive } from '#src/patdown-github-actions-output';
4
+ export { formatPatdownGitHubActionsAnnotations, formatPatdownGitHubActionsSummary, formatPatdownRuleGuidance, patdownLintResultIsNearMiss, } from '#src/patdown-github-actions-summary';
5
+ export { patdownGitHubActionsIsEnabled, readPatdownGitHubStepSummaryPath, } from '#src/patdown-github-actions-env';
3
6
  export { runPatdownCli } from '#src/run-patdown-cli';
4
- export { PatdownJudge, PatdownJudgeFailed, PatdownJudgmentSchema, askPatdownJudge, patdownJudgmentIsYes, patdownYesThreshold, } from '#src/patdown-judge';
7
+ export { PatdownJudge, PatdownJudgeFailed, PatdownJudgmentSchema, askPatdownJudge, locatePatdownEvidence, patdownJudgmentIsYes, patdownYesThreshold, } from '#src/patdown-judge';
5
8
  export { defaultPatdownYesThreshold, decodePatdownYesThreshold, PatdownYesThresholdInvalid, } from '@patdown/rules';
6
9
  export { TypeSafeJudgeLive } from '#src/typesafe-judge';
@@ -0,0 +1,13 @@
1
+ import type { PatdownLintResult } from '#src/patdown-output';
2
+ /** Soft cap so extreme directory labels do not dominate the header line. */
3
+ export declare const patdownConsolePathMaxWidth = 48;
4
+ /** Keep the basename side of long paths: `…/src/patdown-github-actions-summary.ts`. */
5
+ export declare function formatPatdownConsolePath(filePath: string, maxWidth?: number): string;
6
+ /** Quiet one-line lint result. */
7
+ export declare function formatPatdownLintResultLine(result: PatdownLintResult): string;
8
+ /**
9
+ * Verbose local console: one box per rule, files grouped by directory with ├─ headers. Empty
10
+ * results mean the rule matched no files.
11
+ */
12
+ export declare function formatPatdownRuleBlock(ruleTitle: string, results: ReadonlyArray<PatdownLintResult>): string;
13
+ //# sourceMappingURL=patdown-console-rule-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patdown-console-rule-blocks.d.ts","sourceRoot":"","sources":["../src/patdown-console-rule-blocks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAK5D,4EAA4E;AAC5E,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAc5C,uFAAuF;AACvF,wBAAgB,wBAAwB,CACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAmC,GAC3C,MAAM,CAaR;AA0BD,kCAAkC;AAClC,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAI7E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,CAAC,iBAAiB,CAAC,GACvC,MAAM,CA4CR"}
@@ -0,0 +1,80 @@
1
+ import { dirname, basename } from 'node:path';
2
+ import { formatPatdownProbabilityBar } from '#src/patdown-probability-bar';
3
+ const patdownRuleBlockWidth = 64;
4
+ /** Soft cap so extreme directory labels do not dominate the header line. */
5
+ export const patdownConsolePathMaxWidth = 48;
6
+ function formatPatdownConsoleElapsed(elapsedMs) {
7
+ return `${String(elapsedMs)}ms`;
8
+ }
9
+ function padPatdownRuleBlockTitle(title, failCount, judgedCount) {
10
+ const suffix = ` ${String(failCount)}✗ / ${String(judgedCount)}`;
11
+ const prefix = `┌ ${title} `;
12
+ const fill = Math.max(2, patdownRuleBlockWidth - prefix.length - suffix.length);
13
+ return `${prefix}${'─'.repeat(fill)}${suffix}`;
14
+ }
15
+ /** Keep the basename side of long paths: `…/src/patdown-github-actions-summary.ts`. */
16
+ export function formatPatdownConsolePath(filePath, maxWidth = patdownConsolePathMaxWidth) {
17
+ if (filePath.length <= maxWidth)
18
+ return filePath;
19
+ const ellipsis = '…';
20
+ const keep = Math.max(1, maxWidth - ellipsis.length);
21
+ const tail = filePath.slice(-keep);
22
+ const slash = tail.indexOf('/');
23
+ if (slash > 0 && slash < tail.length - 1) {
24
+ return `${ellipsis}${tail.slice(slash)}`;
25
+ }
26
+ return `${ellipsis}${tail}`;
27
+ }
28
+ function patdownConsoleDirectoryLabel(filePath) {
29
+ const directory = dirname(filePath);
30
+ return directory === '.' ? '.' : directory;
31
+ }
32
+ function formatPatdownDirectoryHeader(directory) {
33
+ return `├─ ${formatPatdownConsolePath(directory)}`;
34
+ }
35
+ function formatPatdownRuleBlockRow(result, timeWidth, nameWidth) {
36
+ const mark = result.violated ? '✗' : '✓';
37
+ const bar = formatPatdownProbabilityBar(result.violationProbability);
38
+ const score = result.violationProbability.toFixed(2).padStart(4, ' ');
39
+ const time = formatPatdownConsoleElapsed(result.elapsedMs).padStart(timeWidth, ' ');
40
+ const name = formatPatdownConsolePath(basename(result.filePath)).padEnd(nameWidth, ' ');
41
+ return `│ ${mark} ${bar} ${score} ${time} ${name}`.trimEnd();
42
+ }
43
+ /** Quiet one-line lint result. */
44
+ export function formatPatdownLintResultLine(result) {
45
+ const verdict = result.violated ? 'FAIL' : 'PASS';
46
+ return `${verdict} ${result.filePath}: ${result.ruleTitle}`;
47
+ }
48
+ /**
49
+ * Verbose local console: one box per rule, files grouped by directory with ├─ headers. Empty
50
+ * results mean the rule matched no files.
51
+ */
52
+ export function formatPatdownRuleBlock(ruleTitle, results) {
53
+ const failCount = results.filter((result) => result.violated).length;
54
+ const lines = [padPatdownRuleBlockTitle(ruleTitle, failCount, results.length)];
55
+ lines.push('│');
56
+ if (results.length === 0) {
57
+ lines.push('│ (no files matched)');
58
+ }
59
+ else {
60
+ const ordered = [...results].toSorted((left, right) => left.filePath.localeCompare(right.filePath));
61
+ const timeWidth = Math.max(...ordered.map((result) => formatPatdownConsoleElapsed(result.elapsedMs).length));
62
+ const nameWidth = Math.max(...ordered.map((result) => formatPatdownConsolePath(basename(result.filePath)).length));
63
+ let currentDirectory = null;
64
+ for (const result of ordered) {
65
+ const directory = patdownConsoleDirectoryLabel(result.filePath);
66
+ const startingNewDirectory = directory !== currentDirectory;
67
+ if (startingNewDirectory && currentDirectory !== null) {
68
+ lines.push('│');
69
+ }
70
+ if (startingNewDirectory) {
71
+ lines.push(formatPatdownDirectoryHeader(directory));
72
+ lines.push('│');
73
+ currentDirectory = directory;
74
+ }
75
+ lines.push(formatPatdownRuleBlockRow(result, timeWidth, nameWidth));
76
+ }
77
+ }
78
+ lines.push('└');
79
+ return lines.join('\n');
80
+ }
@@ -0,0 +1,44 @@
1
+ /** One Choice candidate with a known 1-based line span. */
2
+ export type PatdownEvidenceCandidate = {
3
+ readonly id: string;
4
+ readonly startLine: number;
5
+ readonly endLine: number;
6
+ readonly content: string;
7
+ };
8
+ /** Prefer one Choice label per source line while the file stays under this size. */
9
+ export declare const patdownEvidencePerLineMaxCount = 500;
10
+ /** Chunk height when the file is too large for per-line Choice. */
11
+ export declare const patdownEvidenceChunkLineCount = 80;
12
+ /** Cap chunked candidates so the second pass stays bounded. */
13
+ export declare const patdownEvidenceChunkMaxCount = 24;
14
+ /** Truncate long line text used as Choice descriptions. */
15
+ export declare const patdownEvidenceDescriptionMaxChars = 160;
16
+ /** Minimum Choice confidence before a FAIL annotation uses the selected span. */
17
+ export declare const patdownEvidenceMinConfidence = 0.55;
18
+ /** Label returned when no candidate is strong enough. */
19
+ export declare function patdownEvidenceNoMatchChoice(): string;
20
+ /**
21
+ * Build FAIL-only Choice candidates. Prefer one label per line. Past
22
+ * {@link patdownEvidencePerLineMaxCount}, fall back to chunks. Smarter units (functions, headings,
23
+ * hunks) can replace this later without changing the Choice pass.
24
+ */
25
+ export declare function splitPatdownEvidenceCandidates(contents: string, perLineMaxCount?: number, chunkLineCount?: number, chunkMaxCount?: number): ReadonlyArray<PatdownEvidenceCandidate>;
26
+ /** Choice criteria: candidate ids plus noMatch. Descriptions stay short. */
27
+ export type PatdownEvidenceChoiceCriteria = {
28
+ readonly [label: string]: string;
29
+ };
30
+ export declare function patdownEvidenceChoiceCriteria(candidates: ReadonlyArray<PatdownEvidenceCandidate>): PatdownEvidenceChoiceCriteria;
31
+ /** Inputs for FAIL-only evidence Choice state. */
32
+ export type PatdownEvidenceChoiceStateInput = {
33
+ readonly relativePath: string;
34
+ readonly ruleTitle: string;
35
+ readonly ruleBody: string;
36
+ readonly violationProbability: number;
37
+ readonly contents: string;
38
+ readonly candidates: ReadonlyArray<PatdownEvidenceCandidate>;
39
+ };
40
+ /** State for the evidence Choice: original FAIL, full file, and candidate index. */
41
+ export declare function formatPatdownEvidenceChoiceState(input: PatdownEvidenceChoiceStateInput): string;
42
+ /** Instructions for selecting the strongest violating candidate. */
43
+ export declare function patdownEvidenceChoiceInstructions(): string;
44
+ //# sourceMappingURL=patdown-evidence-regions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patdown-evidence-regions.d.ts","sourceRoot":"","sources":["../src/patdown-evidence-regions.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,oFAAoF;AACpF,eAAO,MAAM,8BAA8B,MAAM,CAAA;AAEjD,mEAAmE;AACnE,eAAO,MAAM,6BAA6B,KAAK,CAAA;AAE/C,+DAA+D;AAC/D,eAAO,MAAM,4BAA4B,KAAK,CAAA;AAE9C,2DAA2D;AAC3D,eAAO,MAAM,kCAAkC,MAAM,CAAA;AAErD,iFAAiF;AACjF,eAAO,MAAM,4BAA4B,OAAO,CAAA;AAIhD,yDAAyD;AACzD,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAiED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC7C,QAAQ,EAAE,MAAM,EAChB,eAAe,GAAE,MAAuC,EACxD,cAAc,GAAE,MAAsC,EACtD,aAAa,GAAE,MAAqC,GAClD,aAAa,CAAC,wBAAwB,CAAC,CAQzC;AAED,4EAA4E;AAC5E,MAAM,MAAM,6BAA6B,GAAG;IAC3C,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CAChC,CAAA;AAED,wBAAgB,6BAA6B,CAC5C,UAAU,EAAE,aAAa,CAAC,wBAAwB,CAAC,GACjD,6BAA6B,CAkB/B;AAED,kDAAkD;AAClD,MAAM,MAAM,+BAA+B,GAAG;IAC7C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,wBAAwB,CAAC,CAAA;CAC5D,CAAA;AAED,oFAAoF;AACpF,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,+BAA+B,GAAG,MAAM,CA+B/F;AAED,oEAAoE;AACpE,wBAAgB,iCAAiC,IAAI,MAAM,CAO1D"}
@@ -0,0 +1,124 @@
1
+ /** Prefer one Choice label per source line while the file stays under this size. */
2
+ export const patdownEvidencePerLineMaxCount = 500;
3
+ /** Chunk height when the file is too large for per-line Choice. */
4
+ export const patdownEvidenceChunkLineCount = 80;
5
+ /** Cap chunked candidates so the second pass stays bounded. */
6
+ export const patdownEvidenceChunkMaxCount = 24;
7
+ /** Truncate long line text used as Choice descriptions. */
8
+ export const patdownEvidenceDescriptionMaxChars = 160;
9
+ /** Minimum Choice confidence before a FAIL annotation uses the selected span. */
10
+ export const patdownEvidenceMinConfidence = 0.55;
11
+ const patdownEvidenceNoMatchLabel = 'noMatch';
12
+ /** Label returned when no candidate is strong enough. */
13
+ export function patdownEvidenceNoMatchChoice() {
14
+ return patdownEvidenceNoMatchLabel;
15
+ }
16
+ function normalizePatdownEvidenceLines(contents) {
17
+ const normalized = contents.replace(/\r\n/gu, '\n');
18
+ if (normalized.length === 0)
19
+ return [''];
20
+ // Trailing newline must not create a phantom empty line / endLine past EOF.
21
+ const withoutTrailingNewline = normalized.endsWith('\n') ? normalized.slice(0, -1) : normalized;
22
+ return withoutTrailingNewline.length === 0 ? [''] : withoutTrailingNewline.split('\n');
23
+ }
24
+ function truncatePatdownEvidenceDescription(text) {
25
+ const collapsed = text.replace(/\s+/gu, ' ').trim();
26
+ if (collapsed.length === 0)
27
+ return '(empty line)';
28
+ if (collapsed.length <= patdownEvidenceDescriptionMaxChars)
29
+ return collapsed;
30
+ return `${collapsed.slice(0, patdownEvidenceDescriptionMaxChars - 1)}…`;
31
+ }
32
+ function splitPatdownEvidenceChunks(lines, lineCount, maxChunks) {
33
+ const candidates = [];
34
+ const step = Math.max(1, lineCount);
35
+ for (let startIndex = 0; startIndex < lines.length; startIndex += step) {
36
+ if (candidates.length >= maxChunks)
37
+ break;
38
+ const endIndex = Math.min(lines.length, startIndex + step);
39
+ const startLine = startIndex + 1;
40
+ const endLine = endIndex;
41
+ const id = `c${String(candidates.length + 1)}`;
42
+ candidates.push({
43
+ id,
44
+ startLine,
45
+ endLine,
46
+ content: lines.slice(startIndex, endIndex).join('\n'),
47
+ });
48
+ }
49
+ return candidates;
50
+ }
51
+ function splitPatdownEvidenceLines(lines) {
52
+ return lines.map((content, index) => {
53
+ const startLine = index + 1;
54
+ return {
55
+ id: `L${String(startLine)}`,
56
+ startLine,
57
+ endLine: startLine,
58
+ content,
59
+ };
60
+ });
61
+ }
62
+ /**
63
+ * Build FAIL-only Choice candidates. Prefer one label per line. Past
64
+ * {@link patdownEvidencePerLineMaxCount}, fall back to chunks. Smarter units (functions, headings,
65
+ * hunks) can replace this later without changing the Choice pass.
66
+ */
67
+ export function splitPatdownEvidenceCandidates(contents, perLineMaxCount = patdownEvidencePerLineMaxCount, chunkLineCount = patdownEvidenceChunkLineCount, chunkMaxCount = patdownEvidenceChunkMaxCount) {
68
+ const lines = normalizePatdownEvidenceLines(contents);
69
+ if (lines.length <= perLineMaxCount) {
70
+ return splitPatdownEvidenceLines(lines);
71
+ }
72
+ return splitPatdownEvidenceChunks(lines, chunkLineCount, chunkMaxCount);
73
+ }
74
+ export function patdownEvidenceChoiceCriteria(candidates) {
75
+ const entries = [
76
+ [patdownEvidenceNoMatchLabel, 'No candidate provides clear, direct evidence of the violation'],
77
+ ];
78
+ for (const candidate of candidates) {
79
+ const span = candidate.startLine === candidate.endLine
80
+ ? `Line ${String(candidate.startLine)}`
81
+ : `Lines ${String(candidate.startLine)}-${String(candidate.endLine)}`;
82
+ entries.push([
83
+ candidate.id,
84
+ `${span}: ${truncatePatdownEvidenceDescription(candidate.content)}`,
85
+ ]);
86
+ }
87
+ return Object.fromEntries(entries);
88
+ }
89
+ /** State for the evidence Choice: original FAIL, full file, and candidate index. */
90
+ export function formatPatdownEvidenceChoiceState(input) {
91
+ const mode = input.candidates.length > 0 && input.candidates[0]?.startLine === input.candidates[0]?.endLine
92
+ ? 'per-line'
93
+ : 'chunked';
94
+ const parts = [
95
+ `path: ${input.relativePath}`,
96
+ `noul P(yes): ${String(input.violationProbability)}`,
97
+ `candidate mode: ${mode}`,
98
+ '',
99
+ `# ${input.ruleTitle}`,
100
+ '',
101
+ input.ruleBody,
102
+ '',
103
+ 'Full file:',
104
+ input.contents,
105
+ '',
106
+ 'Candidates (choose the strongest direct evidence of the violation):',
107
+ ];
108
+ for (const candidate of input.candidates) {
109
+ const span = candidate.startLine === candidate.endLine
110
+ ? `line ${String(candidate.startLine)}`
111
+ : `lines ${String(candidate.startLine)}-${String(candidate.endLine)}`;
112
+ parts.push(`- ${candidate.id}: ${span}`);
113
+ }
114
+ return parts.join('\n');
115
+ }
116
+ /** Instructions for selecting the strongest violating candidate. */
117
+ export function patdownEvidenceChoiceInstructions() {
118
+ return [
119
+ 'Which candidate provides the strongest direct evidence that this file violates the rule?',
120
+ 'When candidates are single lines, pick the specific line that contains the violation.',
121
+ 'Select noMatch when no candidate provides sufficient evidence.',
122
+ 'Prefer the clearest violation, not merely related text.',
123
+ ].join(' ');
124
+ }
@@ -0,0 +1,6 @@
1
+ import { Effect } from 'effect';
2
+ /** True when GitHub Actions exposes a step summary file. */
3
+ export declare const patdownGitHubActionsIsEnabled: Effect.Effect<boolean>;
4
+ /** Absolute path of the step summary file when Actions is active. */
5
+ export declare const readPatdownGitHubStepSummaryPath: Effect.Effect<string | null>;
6
+ //# sourceMappingURL=patdown-github-actions-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patdown-github-actions-env.d.ts","sourceRoot":"","sources":["../src/patdown-github-actions-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,MAAM,EAAU,MAAM,QAAQ,CAAA;AAE/C,4DAA4D;AAC5D,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAiB/D,CAAA;AAEF,qEAAqE;AACrE,eAAO,MAAM,gCAAgC,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAWzE,CAAA"}
@@ -0,0 +1,17 @@
1
+ import { Config, Effect, Option } from 'effect';
2
+ /** True when GitHub Actions exposes a step summary file. */
3
+ export const patdownGitHubActionsIsEnabled = Effect.gen(function* () {
4
+ const actions = yield* Config.string('GITHUB_ACTIONS').pipe(Config.option, Effect.orElseSucceed(() => Option.none()));
5
+ const summary = yield* Config.string('GITHUB_STEP_SUMMARY').pipe(Config.option, Effect.orElseSucceed(() => Option.none()));
6
+ return (Option.isSome(actions) &&
7
+ actions.value === 'true' &&
8
+ Option.isSome(summary) &&
9
+ summary.value.length > 0);
10
+ });
11
+ /** Absolute path of the step summary file when Actions is active. */
12
+ export const readPatdownGitHubStepSummaryPath = Effect.gen(function* () {
13
+ if (!(yield* patdownGitHubActionsIsEnabled))
14
+ return null;
15
+ const summary = yield* Config.string('GITHUB_STEP_SUMMARY').pipe(Config.option, Effect.orElseSucceed(() => Option.none()));
16
+ return Option.getOrNull(summary);
17
+ });
@@ -0,0 +1,8 @@
1
+ import { FileSystem, Layer } from 'effect';
2
+ import { PatdownOutput } from '#src/patdown-output';
3
+ /**
4
+ * Human stdout plus GitHub annotations and a step-summary heatmap. Intended when GITHUB_ACTIONS and
5
+ * GITHUB_STEP_SUMMARY are set.
6
+ */
7
+ export declare const PatdownGitHubActionsOutputLive: Layer.Layer<PatdownOutput, never, FileSystem.FileSystem>;
8
+ //# sourceMappingURL=patdown-github-actions-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patdown-github-actions-output.d.ts","sourceRoot":"","sources":["../src/patdown-github-actions-output.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,UAAU,EAAE,KAAK,EAAO,MAAM,QAAQ,CAAA;AAOhE,OAAO,EACN,aAAa,EAIb,MAAM,qBAAqB,CAAA;AAM5B;;;GAGG;AACH,eAAO,MAAM,8BAA8B,EAAE,KAAK,CAAC,KAAK,CACvD,aAAa,EACb,KAAK,EACL,UAAU,CAAC,UAAU,CAsErB,CAAA"}