@clearlydev/nx-archweave 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +265 -2
- package/dist/bin/review-verdict.js +330 -0
- package/dist/eslint/fsd-boundaries.d.ts +119 -0
- package/dist/eslint/fsd-boundaries.js +144 -0
- package/dist/eslint/index.d.ts +78 -0
- package/dist/eslint/index.js +256 -0
- package/dist/eslint/tsconfig-aliases.d.ts +18 -0
- package/dist/eslint/tsconfig-aliases.js +114 -0
- package/dist/generators/add-agent-rules/generator.js +550 -0
- package/dist/generators/add-agent-rules/render.js +46 -0
- package/dist/generators/add-backend/generator.js +784 -0
- package/dist/generators/add-backend/render.js +102 -0
- package/dist/generators/add-fsd-lite/generator.js +553 -0
- package/dist/generators/add-fsd-lite/render.js +72 -0
- package/dist/generators/add-github-templates/files/en/codeowners/.github/CODEOWNERS.template +4 -0
- package/dist/generators/add-github-templates/files/en/issue/.github/ISSUE_TEMPLATE/bug-report.yml.template +28 -0
- package/dist/generators/add-github-templates/files/en/issue/.github/ISSUE_TEMPLATE/feature-request.yml.template +17 -0
- package/dist/generators/add-github-templates/files/en/pr/.github/PULL_REQUEST_TEMPLATE.md.template +12 -0
- package/dist/generators/add-github-templates/files/ko/codeowners/.github/CODEOWNERS.template +4 -0
- package/dist/generators/add-github-templates/files/ko/issue/.github/ISSUE_TEMPLATE/bug-report.yml.template +28 -0
- package/dist/generators/add-github-templates/files/ko/issue/.github/ISSUE_TEMPLATE/feature-request.yml.template +17 -0
- package/dist/generators/add-github-templates/files/ko/pr/.github/PULL_REQUEST_TEMPLATE.md.template +12 -0
- package/dist/generators/add-github-templates/generator.js +125 -0
- package/dist/generators/add-github-templates/render.js +26 -0
- package/dist/generators/add-integration/generator.js +818 -0
- package/dist/generators/add-integration/render.js +34 -0
- package/dist/generators/add-review/generator.js +921 -0
- package/dist/generators/add-review/render.js +96 -0
- package/dist/generators/allow-builds/generator.js +436 -0
- package/dist/generators/allow-builds/render.js +82 -0
- package/dist/generators/doctor/generator.js +2431 -0
- package/dist/generators/doctor/integration-report.js +70 -0
- package/dist/generators/doctor/render.js +10 -0
- package/dist/generators/entity/generator.js +395 -0
- package/dist/generators/feature/generator.js +395 -0
- package/dist/generators/init/generator.js +3211 -0
- package/dist/generators/init/render.js +125 -0
- package/dist/generators/init/validation.js +74 -0
- package/dist/generators/install-integration/generator.js +1233 -0
- package/dist/generators/install-integration/render.js +128 -0
- package/dist/generators/page/generator.js +395 -0
- package/dist/generators/record-integration/generator.js +852 -0
- package/dist/generators/record-integration/render.js +31 -0
- package/dist/generators/shared/dry-run.js +12 -0
- package/dist/generators/shared/frontend-app.js +57 -0
- package/dist/generators/shared/generator.js +395 -0
- package/dist/generators/shared/project-type.js +39 -0
- package/dist/generators/shared/record-pipeline.js +34 -0
- package/dist/generators/shared/run-nx.js +24 -0
- package/dist/generators/shared/template-files.js +38 -0
- package/dist/generators/shared/toolkit-config-file.js +110 -0
- package/dist/generators/slice/core.js +185 -0
- package/dist/generators/slice/files/index/index.ts.template +14 -0
- package/dist/generators/slice/files/module/__name__.spec.ts.template +11 -0
- package/dist/generators/slice/files/module/__name__.ts.template +11 -0
- package/dist/generators/slice/files/segment/index.ts.template +20 -0
- package/dist/generators/slice/render.js +70 -0
- package/dist/generators/widget/generator.js +395 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +680 -0
- package/dist/lib/nx-plugin.d.ts +2 -0
- package/dist/lib/nx-plugin.js +7 -0
- package/dist/runtime/integrations-composition-root.js +20 -0
- package/dist/runtime/node-command-runner.js +81 -0
- package/generators.json +85 -0
- package/package.json +95 -4
- package/src/generators/add-agent-rules/schema.json +43 -0
- package/src/generators/add-backend/schema.json +39 -0
- package/src/generators/add-fsd-lite/schema.json +43 -0
- package/src/generators/add-github-templates/schema.json +35 -0
- package/src/generators/add-integration/schema.json +35 -0
- package/src/generators/add-review/schema.json +72 -0
- package/src/generators/allow-builds/schema.json +20 -0
- package/src/generators/doctor/schema.json +20 -0
- package/src/generators/entity/schema.json +37 -0
- package/src/generators/feature/schema.json +37 -0
- package/src/generators/init/schema.json +98 -0
- package/src/generators/install-integration/schema.json +35 -0
- package/src/generators/page/schema.json +53 -0
- package/src/generators/record-integration/schema.json +26 -0
- package/src/generators/shared/schema.json +32 -0
- package/src/generators/widget/schema.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nx-archweave contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,266 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @clearlydev/nx-archweave
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Nx generators that get a workspace ready to work on with one preset:
|
|
4
|
+
|
|
5
|
+
- FSD Lite structure with enforced module boundaries
|
|
6
|
+
- AI agent rules
|
|
7
|
+
- GitHub templates
|
|
8
|
+
- optional AI PR review (Claude, Gemini, GPT, Cursor)
|
|
9
|
+
|
|
10
|
+
Starting a brand-new workspace instead? Use [`create-nx-archweave`](https://www.npmjs.com/package/create-nx-archweave). It creates the workspace, installs this plugin, and runs `init`, all in one command.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Run one of these at the workspace root, with the workspace's package manager:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @clearlydev/nx-archweave # npm
|
|
18
|
+
pnpm add -D -w @clearlydev/nx-archweave # pnpm
|
|
19
|
+
yarn add -D @clearlydev/nx-archweave # yarn berry (yarn 1: add -W)
|
|
20
|
+
bun add -d @clearlydev/nx-archweave # bun
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then confirm the install. `doctor` is read-only:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm exec nx -- g @clearlydev/nx-archweave:doctor # npm
|
|
27
|
+
pnpm nx g @clearlydev/nx-archweave:doctor # pnpm
|
|
28
|
+
yarn nx g @clearlydev/nx-archweave:doctor # yarn
|
|
29
|
+
bun nx g @clearlydev/nx-archweave:doctor # bun
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- **Requirements:** Node >= 22.18, plus one of npm, pnpm >= 10, yarn (classic or berry) or bun. All four package managers are first-class.
|
|
33
|
+
- Generators detect the workspace's real package manager from its lockfiles.
|
|
34
|
+
- The examples below use `pnpm nx g`. With another package manager, run the same generator the way the `doctor` commands above show.
|
|
35
|
+
- The package ships as a self-contained bundle. Its only runtime dependency is `@nx/devkit`.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
Preview the full run first. This writes nothing:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pnpm nx g @clearlydev/nx-archweave:init --preset=team --frontend=next --dryRun
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then run it for real:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm nx g @clearlydev/nx-archweave:init --preset=team --frontend=next
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- `init` runs the content generators below according to the preset (`quick-start` | `solo` | `team`).
|
|
52
|
+
- `init` requires a clean git working tree. That way you can always undo a partial run with a single `git restore .`.
|
|
53
|
+
- `--allowDirty` opts out of the clean-tree check. The report then warns you that the rollback scope is wider.
|
|
54
|
+
- `--locale=ko|en` sets the language of generated content. Reports are always in English.
|
|
55
|
+
|
|
56
|
+
## Generators
|
|
57
|
+
|
|
58
|
+
| Generator | What it does |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `init` | Runs the five `add-*` generators by preset |
|
|
61
|
+
| `doctor` | Read-only diagnosis; never writes |
|
|
62
|
+
| `add-fsd-lite` | Wires in the FSD boundaries ESLint rule |
|
|
63
|
+
| `add-agent-rules` | Writes rules into each AI host's file |
|
|
64
|
+
| `add-github-templates` | Issue Forms, PR template, CODEOWNERS |
|
|
65
|
+
| `add-review` | AI PR review workflow |
|
|
66
|
+
| `add-backend` | Wires a backend app in |
|
|
67
|
+
| `page` / `widget` / `feature` / `entity` / `shared` | Scaffold an FSD slice or segment |
|
|
68
|
+
| `add-integration` | Previews a catalog plugin's install flow |
|
|
69
|
+
| `install-integration` | Installs a catalog plugin (with `--apply`) |
|
|
70
|
+
| `record-integration` | Records a plugin you installed by hand |
|
|
71
|
+
| `allow-builds` | pnpm only: allows skipped build scripts |
|
|
72
|
+
|
|
73
|
+
In the option lists below:
|
|
74
|
+
|
|
75
|
+
- \* marks a required option.
|
|
76
|
+
- † marks an option that's required until `init` has recorded it in `toolkit.config.json`. After that, if you leave it out, the generator uses the recorded value.
|
|
77
|
+
|
|
78
|
+
### `init`
|
|
79
|
+
|
|
80
|
+
A preset-driven pipeline over the five `add-*` generators: `add-fsd-lite`, `add-agent-rules`, `add-github-templates`, `add-review` and `add-backend`. After the run, `init` does a best-effort validation (actionlint, `nx graph`).
|
|
81
|
+
|
|
82
|
+
Options: `--preset`†, `--frontend`†, `--backend`, `--packageManager`, `--locale`, `--aiHosts`, `--reviewProvider`, `--fsd`, `--agentRules`, `--githubTemplates`, `--review`, `--applyEslint`, `--allowDirty`, `--dryRun`
|
|
83
|
+
|
|
84
|
+
### `doctor`
|
|
85
|
+
|
|
86
|
+
A read-only diagnosis. `doctor` never writes. It checks:
|
|
87
|
+
|
|
88
|
+
- workspace, package manager and framework detection
|
|
89
|
+
- the nx version range
|
|
90
|
+
- whether the boundary tags exist
|
|
91
|
+
- FSD-like structure
|
|
92
|
+
- review consistency
|
|
93
|
+
- drift states
|
|
94
|
+
|
|
95
|
+
Options: `--liveIntegrations`, `--json`
|
|
96
|
+
|
|
97
|
+
### `add-fsd-lite`
|
|
98
|
+
|
|
99
|
+
Wires the `archweave/fsd-boundaries` ESLint rule into the frontend app's config. The rule ships as `@clearlydev/nx-archweave/eslint`.
|
|
100
|
+
|
|
101
|
+
- With `--applyEslint`, the generator writes the config. Without it, you get a paste-ready snippet.
|
|
102
|
+
- An unrecognized config is never edited.
|
|
103
|
+
- Prints the layer guide (Next.js: `pages` → `_pages/`).
|
|
104
|
+
- Creates no folders.
|
|
105
|
+
- `--steiger` adds the official FSD linter as an opt-in `fsd-check` target (Node ≥ 22.18).
|
|
106
|
+
|
|
107
|
+
Options: `--frontend`†, `--project`, `--enforcement`, `--applyEslint`, `--steiger`, `--locale`, `--dryRun`
|
|
108
|
+
|
|
109
|
+
### `add-agent-rules`
|
|
110
|
+
|
|
111
|
+
Writes nx-archweave's rules as one marked block in the file each AI host reads:
|
|
112
|
+
|
|
113
|
+
- `CLAUDE.md` for Claude Code
|
|
114
|
+
- `AGENTS.md` for Codex, Cursor and Gemini CLI
|
|
115
|
+
|
|
116
|
+
The generator never touches the text around the block.
|
|
117
|
+
|
|
118
|
+
Options: `--hosts`, `--teamRules`, `--locale`, `--dryRun`
|
|
119
|
+
|
|
120
|
+
### `add-github-templates`
|
|
121
|
+
|
|
122
|
+
Adds GitHub Issue Forms, a PR template and CODEOWNERS. The generator always skips files that already exist.
|
|
123
|
+
|
|
124
|
+
Options: `--issue`, `--pr`, `--codeowners`, `--locale`, `--dryRun`
|
|
125
|
+
|
|
126
|
+
### `add-review`
|
|
127
|
+
|
|
128
|
+
Installs the AI PR review workflow and the `review` field in `toolkit.config.json`.
|
|
129
|
+
|
|
130
|
+
- `--provider`, `--mode`, `--maxRounds`: if you leave one out, the generator uses the value already recorded
|
|
131
|
+
- `--onConflict`, `--locale`, `--dryRun`
|
|
132
|
+
|
|
133
|
+
### `add-backend`
|
|
134
|
+
|
|
135
|
+
Wires a backend app in:
|
|
136
|
+
|
|
137
|
+
- a `shared-types` lib
|
|
138
|
+
- `scope:` tags
|
|
139
|
+
- boundary constraints (written only with `--applyEslint`)
|
|
140
|
+
|
|
141
|
+
If the `api` project is missing, the generator refuses and gives you the official commands to copy and paste.
|
|
142
|
+
|
|
143
|
+
Options: `--backend`\*, `--apiName`, `--frontendProject`, `--applyEslint`, `--dryRun`
|
|
144
|
+
|
|
145
|
+
### `page` / `widget` / `feature` / `entity` / `shared`
|
|
146
|
+
|
|
147
|
+
`page`, `widget`, `feature` and `entity` scaffold a slice: `index.ts` + `model/`, plus `--segments` extras. `shared` scaffolds a purpose-named segment instead. Each generator writes to its own layer in the app:
|
|
148
|
+
|
|
149
|
+
- `page` → `src/pages` (`_pages` on Next.js)
|
|
150
|
+
- `widget` → `src/widgets`
|
|
151
|
+
- `feature` → `src/features`
|
|
152
|
+
- `entity` → `src/entities`
|
|
153
|
+
- `shared` → `src/shared`
|
|
154
|
+
|
|
155
|
+
All five generators create the layer directory if it's missing, skip slices that have the same name, and check that `--publicApiName` is a usable identifier.
|
|
156
|
+
|
|
157
|
+
- Options: `--name`\*, `--publicApiName`, `--segments`, `--project`, `--locale`, `--dryRun`
|
|
158
|
+
- `--segments` isn't available on `shared`, because there the segment is the name.
|
|
159
|
+
- `page` also takes `--frontend`.
|
|
160
|
+
|
|
161
|
+
### `add-integration`
|
|
162
|
+
|
|
163
|
+
**Preview only.** Shows a catalog plugin's official install flow: source, license, manual steps and uninstall. `add-integration` installs nothing and writes nothing.
|
|
164
|
+
|
|
165
|
+
Options: `--tool`\*, `--target`, `--scope`, `--versionRange`, `--dryRun`
|
|
166
|
+
|
|
167
|
+
### `install-integration`
|
|
168
|
+
|
|
169
|
+
With `--apply`, `install-integration`:
|
|
170
|
+
|
|
171
|
+
- runs the host's official CLI install of a catalog plugin (project scope by default)
|
|
172
|
+
- re-verifies the install live
|
|
173
|
+
- records the manifest entry
|
|
174
|
+
- declares the tool in `toolkit.config.json`
|
|
175
|
+
|
|
176
|
+
Without `--apply`, you get a preview.
|
|
177
|
+
|
|
178
|
+
Options: `--tool`\*, `--target`, `--scope`, `--versionRange`, `--apply`
|
|
179
|
+
|
|
180
|
+
### `record-integration`
|
|
181
|
+
|
|
182
|
+
Verifies a manually completed install live. With `--apply`, it records exactly one entry in `archweave-integrations.json`, and only when the authoritative signal succeeds.
|
|
183
|
+
|
|
184
|
+
Options: `--tool`\*, `--target`, `--apply`
|
|
185
|
+
|
|
186
|
+
### `allow-builds`
|
|
187
|
+
|
|
188
|
+
pnpm only. Allows dependency build scripts that pnpm skipped or was told to deny. With `--apply`, `allow-builds` sets those packages to `true` in `pnpm-workspace.yaml` and runs `pnpm rebuild`. Without `--apply`, you get a preview.
|
|
189
|
+
|
|
190
|
+
Options: `--packages`\*, `--apply`
|
|
191
|
+
|
|
192
|
+
## Presets
|
|
193
|
+
|
|
194
|
+
| | `quick-start` | `solo` | `team` |
|
|
195
|
+
| --- | --- | --- | --- |
|
|
196
|
+
| FSD Lite | on | on | on |
|
|
197
|
+
| FSD rule severity | `warn` | `warn` | `error` |
|
|
198
|
+
| Agent rules | the AI hosts' files | the AI hosts' files | the AI hosts' files + Team Rules section |
|
|
199
|
+
| GitHub templates | PR only | PR only | Issue + PR |
|
|
200
|
+
| Review default | off (opt-in) | on | on |
|
|
201
|
+
| Review mode | `fast` | `fast` | `standard` |
|
|
202
|
+
|
|
203
|
+
Explicit flags always win over preset defaults.
|
|
204
|
+
|
|
205
|
+
## AI PR review
|
|
206
|
+
|
|
207
|
+
Review is fully optional and isn't tied to one provider.
|
|
208
|
+
|
|
209
|
+
- If review is disabled, zero review artifacts are generated.
|
|
210
|
+
- If review is enabled, the workflow **green-skips** (it never fails) until you register the provider's secret.
|
|
211
|
+
- A round marker stops the workflow after `--maxRounds` reviews per PR (1–5, default 3).
|
|
212
|
+
|
|
213
|
+
| `--provider` | Workflow file | Required secret |
|
|
214
|
+
| --- | --- | --- |
|
|
215
|
+
| `claude` (default) | `.github/workflows/claude-code-review.yml` | `CLAUDE_CODE_OAUTH_TOKEN` |
|
|
216
|
+
| `gemini` | `.github/workflows/gemini-code-review.yml` | `GEMINI_API_KEY` |
|
|
217
|
+
| `gpt` | `.github/workflows/gpt-code-review.yml` | `OPENAI_API_KEY` |
|
|
218
|
+
| `cursor` | `.github/workflows/cursor-code-review.yml` | `CURSOR_API_KEY` |
|
|
219
|
+
|
|
220
|
+
Modes:
|
|
221
|
+
|
|
222
|
+
- `fast` and `standard` review correctness & security.
|
|
223
|
+
- `thorough` adds an over-engineering pass.
|
|
224
|
+
|
|
225
|
+
Findings are prioritized P0–P3/Nit per the bundled review policy.
|
|
226
|
+
|
|
227
|
+
## Behavioral contracts
|
|
228
|
+
|
|
229
|
+
Every generator keeps four promises:
|
|
230
|
+
|
|
231
|
+
- `--dryRun` previews fully and writes nothing.
|
|
232
|
+
- Running it again against identical content reports "no change".
|
|
233
|
+
- A failed precondition produces a refusal report, not an exception.
|
|
234
|
+
- Conflicts are never silently overwritten.
|
|
235
|
+
|
|
236
|
+
## Optional plugins per AI host (experimental)
|
|
237
|
+
|
|
238
|
+
Three generators work with plugins from a reviewed **catalog**:
|
|
239
|
+
|
|
240
|
+
- `add-integration` previews a plugin.
|
|
241
|
+
- `install-integration` installs a plugin (with `--apply`).
|
|
242
|
+
- `record-integration` records a plugin.
|
|
243
|
+
|
|
244
|
+
Installs go through the AI host's own official installer, in project scope by default.
|
|
245
|
+
|
|
246
|
+
- The catalog is data. Claude Code is the only host with an adapter today, and [ponytail](https://github.com/DietrichGebert/ponytail) is its first entry.
|
|
247
|
+
- Recording requires the live `claude plugin list --json` signal to confirm that the plugin is installed **and** enabled.
|
|
248
|
+
- A disabled plugin, an ambiguous match or a CLI error produces zero writes, never a false "verified".
|
|
249
|
+
- `doctor` reports the declared, recorded and detected state of each plugin. `--liveIntegrations` re-verifies live.
|
|
250
|
+
- Nothing here runs unless you ask.
|
|
251
|
+
|
|
252
|
+
## More
|
|
253
|
+
|
|
254
|
+
Full documentation and the Korean README live in the [repository](https://github.com/clearlydev/nx-archweave).
|
|
255
|
+
|
|
256
|
+
## Contributing
|
|
257
|
+
|
|
258
|
+
- `nx build @clearlydev/nx-archweave` runs tsc, for local dev and typecheck.
|
|
259
|
+
- `nx test` runs Jest.
|
|
260
|
+
- `nx bundle` (depends on `build`) produces the publishable esbuild bundle in `dist/`. Internal workspace packages are inlined, so the tarball carries no `workspace:` deps.
|
|
261
|
+
- `prepack` re-runs `bundle` and strips `workspace:` ranges from the packed manifest only. It's interruption-safe: `postpack` restores the on-disk `package.json`.
|
|
262
|
+
- To verify a release candidate, run `node tools/verify-tarball.mjs` at the repo root.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin/review-verdict.ts
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { realpathSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
// ../review-policy/dist/schema/review-finding.js
|
|
8
|
+
function computeVerdict(findings) {
|
|
9
|
+
if (findings.some((f) => f.priority === "P0" || f.priority === "P1"))
|
|
10
|
+
return "BLOCK";
|
|
11
|
+
if (findings.some((f) => f.priority === "P2"))
|
|
12
|
+
return "CHANGES_RECOMMENDED";
|
|
13
|
+
if (findings.length > 0)
|
|
14
|
+
return "APPROVE_WITH_NOTES";
|
|
15
|
+
return "APPROVE";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ../review-policy/dist/verdict-gate/findings-block.js
|
|
19
|
+
var FINDINGS_MARKER = "<!-- archweave-findings-v1 -->";
|
|
20
|
+
var FINDINGS_SCHEMA_ID = "archweave/findings@1";
|
|
21
|
+
var PRIORITIES = ["P0", "P1", "P2", "P3", "Nit"];
|
|
22
|
+
function findingKey(f) {
|
|
23
|
+
return `${f.file}:${f.lineRange.start}:${f.category}`;
|
|
24
|
+
}
|
|
25
|
+
function extractFindingsBlock(commentBody) {
|
|
26
|
+
const markerIndex = commentBody.lastIndexOf(FINDINGS_MARKER);
|
|
27
|
+
if (markerIndex === -1)
|
|
28
|
+
return { status: "missing" };
|
|
29
|
+
const after = commentBody.slice(markerIndex + FINDINGS_MARKER.length);
|
|
30
|
+
const open = /```json[ \t]*\r?\n/.exec(after);
|
|
31
|
+
if (!open)
|
|
32
|
+
return { status: "invalid" };
|
|
33
|
+
const contentStart = open.index + open[0].length;
|
|
34
|
+
let parsed;
|
|
35
|
+
let found = false;
|
|
36
|
+
for (let close = after.indexOf("```", contentStart); close !== -1; close = after.indexOf("```", close + 3)) {
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(after.slice(contentStart, close));
|
|
39
|
+
found = true;
|
|
40
|
+
break;
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!found)
|
|
45
|
+
return { status: "invalid" };
|
|
46
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
47
|
+
return { status: "invalid" };
|
|
48
|
+
const obj = parsed;
|
|
49
|
+
if (obj.schema !== FINDINGS_SCHEMA_ID || !Array.isArray(obj.findings))
|
|
50
|
+
return { status: "invalid" };
|
|
51
|
+
const findings = [];
|
|
52
|
+
for (const raw of obj.findings) {
|
|
53
|
+
const finding = normalizeFinding(raw);
|
|
54
|
+
if (finding === null)
|
|
55
|
+
return { status: "invalid" };
|
|
56
|
+
findings.push(finding);
|
|
57
|
+
}
|
|
58
|
+
return { status: "ok", findings };
|
|
59
|
+
}
|
|
60
|
+
function normalizeFinding(raw) {
|
|
61
|
+
if (raw === null || typeof raw !== "object")
|
|
62
|
+
return null;
|
|
63
|
+
const r = raw;
|
|
64
|
+
const lineRange = r["lineRange"];
|
|
65
|
+
if (!PRIORITIES.includes(r["priority"]) || typeof r["category"] !== "string" || typeof r["title"] !== "string" || typeof r["file"] !== "string" || lineRange === null || lineRange === void 0 || typeof lineRange !== "object" || typeof lineRange.start !== "number" || !Number.isInteger(lineRange.start) || lineRange.start < 1) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const str = (value) => typeof value === "string" ? value : "";
|
|
69
|
+
return {
|
|
70
|
+
priority: r["priority"],
|
|
71
|
+
category: r["category"],
|
|
72
|
+
title: r["title"],
|
|
73
|
+
file: r["file"],
|
|
74
|
+
lineRange: { start: lineRange.start, ...typeof lineRange.end === "number" ? { end: lineRange.end } : {} },
|
|
75
|
+
evidence: str(r["evidence"]),
|
|
76
|
+
impact: str(r["impact"]),
|
|
77
|
+
reproductionCondition: str(r["reproductionCondition"]),
|
|
78
|
+
recommendation: str(r["recommendation"]),
|
|
79
|
+
confidence: r["confidence"] === "plausible" ? "plausible" : "confirmed"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ../review-policy/dist/renderers/render-review-comment.js
|
|
84
|
+
var COPY = {
|
|
85
|
+
en: {
|
|
86
|
+
terminationBanner: (maxRounds) => `> \u26A0\uFE0F **Automated review ended.** The maximum of ${maxRounds} review rounds has been reached with blocking findings (P0/P1) still open. Human review is required.`,
|
|
87
|
+
verdictLines: {
|
|
88
|
+
BLOCK: "**Verdict: BLOCK** \u2014 P0/P1 findings must be resolved before this change can merge.",
|
|
89
|
+
CHANGES_RECOMMENDED: "**Verdict: CHANGES_RECOMMENDED** \u2014 P2 findings should be addressed.",
|
|
90
|
+
APPROVE_WITH_NOTES: "**Verdict: APPROVE_WITH_NOTES** \u2014 only advisory notes below; safe to merge.",
|
|
91
|
+
APPROVE: "**Verdict: APPROVE** \u2014 no findings."
|
|
92
|
+
},
|
|
93
|
+
mainSection: "## Correctness & Security",
|
|
94
|
+
overEngineeringSection: "## Over-engineering",
|
|
95
|
+
nitSection: "## Nits",
|
|
96
|
+
roundHistoryHeading: "**Round history:**",
|
|
97
|
+
roundLine: (round, fixed, remaining) => `- round ${round}: ${fixed} fixed, ${remaining} remaining`,
|
|
98
|
+
fieldLabels: {
|
|
99
|
+
file: "File",
|
|
100
|
+
evidence: "Evidence",
|
|
101
|
+
impact: "Impact",
|
|
102
|
+
reproduction: "Reproduction",
|
|
103
|
+
recommendation: "Recommendation",
|
|
104
|
+
confidence: "Confidence"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
ko: {
|
|
108
|
+
terminationBanner: (maxRounds) => `> \u26A0\uFE0F **\uC790\uB3D9 \uB9AC\uBDF0 \uC885\uB8CC.** \uCD5C\uB300 ${maxRounds}\uD68C\uC758 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC\uC5D0 \uB3C4\uB2EC\uD588\uC9C0\uB9CC \uBCD1\uD569\uC744 \uB9C9\uB294 finding(P0/P1)\uC774 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4. \uC0AC\uB78C\uC758 \uB9AC\uBDF0\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.`,
|
|
109
|
+
verdictLines: {
|
|
110
|
+
BLOCK: "**\uD310\uC815: BLOCK** \u2014 \uBCD1\uD569 \uC804\uC5D0 P0/P1 finding\uC744 \uD574\uACB0\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
111
|
+
CHANGES_RECOMMENDED: "**\uD310\uC815: CHANGES_RECOMMENDED** \u2014 P2 finding\uC744 \uD574\uACB0\uD558\uB294 \uAC83\uC744 \uAD8C\uC7A5\uD569\uB2C8\uB2E4.",
|
|
112
|
+
APPROVE_WITH_NOTES: "**\uD310\uC815: APPROVE_WITH_NOTES** \u2014 \uCC38\uACE0\uC6A9 \uB178\uD2B8\uB9CC \uC788\uC2B5\uB2C8\uB2E4. \uBCD1\uD569\uD574\uB3C4 \uC548\uC804\uD569\uB2C8\uB2E4.",
|
|
113
|
+
APPROVE: "**\uD310\uC815: APPROVE** \u2014 finding\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."
|
|
114
|
+
},
|
|
115
|
+
mainSection: "## \uC815\uD655\uC131 & \uBCF4\uC548",
|
|
116
|
+
overEngineeringSection: "## \uACFC\uC789 \uC124\uACC4",
|
|
117
|
+
nitSection: "## \uC0AC\uC18C\uD55C \uC9C0\uC801 (Nit)",
|
|
118
|
+
roundHistoryHeading: "**\uB77C\uC6B4\uB4DC \uC774\uB825:**",
|
|
119
|
+
roundLine: (round, fixed, remaining) => `- ${round}\uB77C\uC6B4\uB4DC: \uD574\uACB0 ${fixed}\uAC74, \uBBF8\uD574\uACB0 ${remaining}\uAC74`,
|
|
120
|
+
fieldLabels: {
|
|
121
|
+
file: "\uD30C\uC77C",
|
|
122
|
+
evidence: "\uADFC\uAC70",
|
|
123
|
+
impact: "\uC601\uD5A5",
|
|
124
|
+
reproduction: "\uC7AC\uD604 \uC870\uAC74",
|
|
125
|
+
recommendation: "\uAD8C\uC7A5 \uC870\uCE58",
|
|
126
|
+
confidence: "\uD655\uC2E0\uB3C4"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
function verdictLine(verdict, locale) {
|
|
131
|
+
return COPY[locale].verdictLines[verdict];
|
|
132
|
+
}
|
|
133
|
+
function terminationBannerLine(maxRounds, locale) {
|
|
134
|
+
return COPY[locale].terminationBanner(maxRounds);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ../review-policy/dist/verdict-gate/verdict-comment.js
|
|
138
|
+
var VERDICT_MARKER = "<!-- archweave-verdict -->";
|
|
139
|
+
var HISTORY_HEADING = { en: "**Round history:**", ko: "**\uB77C\uC6B4\uB4DC \uC774\uB825:**" };
|
|
140
|
+
var ROUND_LINE = {
|
|
141
|
+
en: (round, fixed, remaining) => `- round ${round}: ${fixed} fixed, ${remaining} remaining`,
|
|
142
|
+
ko: (round, fixed, remaining) => `- ${round}\uCC28: ${fixed}\uAC74 \uD574\uACB0, ${remaining}\uAC74 \uC794\uC874`
|
|
143
|
+
};
|
|
144
|
+
var UNPARSEABLE_NOTICE = {
|
|
145
|
+
en: "\u26A0\uFE0F The review comment could not be machine-read (missing or invalid findings block). The verdict gate stays green; please review this PR manually.",
|
|
146
|
+
ko: "\u26A0\uFE0F \uB9AC\uBDF0 \uCF54\uBA58\uD2B8\uB97C \uAE30\uACC4 \uD310\uB3C5\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(findings \uBE14\uB85D \uB204\uB77D \uB610\uB294 \uC190\uC0C1). verdict \uAC8C\uC774\uD2B8\uB294 \uB179\uC0C9\uC73C\uB85C \uB450\uB2C8 \uC774 PR\uC740 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uB9AC\uBDF0\uD574 \uC8FC\uC138\uC694."
|
|
147
|
+
};
|
|
148
|
+
var NO_ROUND_NOTICE = {
|
|
149
|
+
en: "\u26A0\uFE0F This run of the review posted no summary comment for the current commit, so there is no verdict for it (an earlier round is never reused). The verdict gate stays green; please review this PR manually.",
|
|
150
|
+
ko: "\u26A0\uFE0F \uC774\uBC88 \uB9AC\uBDF0 \uC2E4\uD589\uC774 \uD604\uC7AC \uCEE4\uBC0B\uC5D0 \uB300\uD55C \uC694\uC57D \uCF54\uBA58\uD2B8\uB97C \uAC8C\uC2DC\uD558\uC9C0 \uC54A\uC544 verdict\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4(\uC774\uC804 \uB77C\uC6B4\uB4DC\uB294 \uC7AC\uC0AC\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). verdict \uAC8C\uC774\uD2B8\uB294 \uB179\uC0C9\uC73C\uB85C \uB450\uB2C8 \uC774 PR\uC740 \uC0AC\uB78C\uC774 \uC9C1\uC811 \uB9AC\uBDF0\uD574 \uC8FC\uC138\uC694."
|
|
151
|
+
};
|
|
152
|
+
var HEAD_LINE = {
|
|
153
|
+
en: (sha) => `<sub>Commit \`${sha.slice(0, 7)}\` \xB7 this comment is updated on every review round.</sub>`,
|
|
154
|
+
ko: (sha) => `<sub>\uCEE4\uBC0B \`${sha.slice(0, 7)}\` \xB7 \uC774 \uCF54\uBA58\uD2B8\uB294 \uB9AC\uBDF0 \uB77C\uC6B4\uB4DC\uB9C8\uB2E4 \uAC31\uC2E0\uB429\uB2C8\uB2E4.</sub>`
|
|
155
|
+
};
|
|
156
|
+
function withHead(lines, locale, headSha) {
|
|
157
|
+
return (headSha === void 0 ? lines : [...lines, "", HEAD_LINE[locale](headSha)]).join("\n");
|
|
158
|
+
}
|
|
159
|
+
function renderUnparseableNotice(locale, headSha) {
|
|
160
|
+
return withHead([VERDICT_MARKER, UNPARSEABLE_NOTICE[locale]], locale, headSha);
|
|
161
|
+
}
|
|
162
|
+
function renderNoRoundNotice(locale, headSha) {
|
|
163
|
+
return withHead([VERDICT_MARKER, NO_ROUND_NOTICE[locale]], locale, headSha);
|
|
164
|
+
}
|
|
165
|
+
function renderVerdictComment(input) {
|
|
166
|
+
const lines = [VERDICT_MARKER];
|
|
167
|
+
if (input.isFinalRound && input.verdict === "BLOCK") {
|
|
168
|
+
lines.push(terminationBannerLine(input.maxRounds, input.locale), "");
|
|
169
|
+
}
|
|
170
|
+
lines.push(verdictLine(input.verdict, input.locale));
|
|
171
|
+
if (input.history.length > 1) {
|
|
172
|
+
lines.push("", HISTORY_HEADING[input.locale]);
|
|
173
|
+
for (let i = 0; i < input.history.length; i++) {
|
|
174
|
+
if (i === 0)
|
|
175
|
+
continue;
|
|
176
|
+
const entry = input.history[i];
|
|
177
|
+
lines.push(ROUND_LINE[input.locale](entry.round, entry.fixedFindingKeys.length, entry.remainingFindingKeys.length));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return withHead(lines, input.locale, input.headSha);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ../review-policy/dist/verdict-gate/verdict-gate.js
|
|
184
|
+
var ROUND_MARKER = "<!-- archweave-review-round -->";
|
|
185
|
+
function collectRounds(comments) {
|
|
186
|
+
return comments.filter((c) => c.body.includes(ROUND_MARKER) && !c.body.includes(VERDICT_MARKER)).map((c, index) => ({ round: index + 1, id: c.id, extraction: extractFindingsBlock(c.body) }));
|
|
187
|
+
}
|
|
188
|
+
function computeRoundHistory(rounds) {
|
|
189
|
+
const parsed = rounds.filter((r) => r.extraction.status === "ok");
|
|
190
|
+
const history = [];
|
|
191
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
192
|
+
const keys = new Set(parsed[i].extraction.findings.map(findingKey));
|
|
193
|
+
if (i === 0) {
|
|
194
|
+
history.push({ round: parsed[i].round, fixedFindingKeys: [], remainingFindingKeys: [...keys] });
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const previous = new Set(parsed[i - 1].extraction.findings.map(findingKey));
|
|
198
|
+
history.push({
|
|
199
|
+
round: parsed[i].round,
|
|
200
|
+
fixedFindingKeys: [...previous].filter((key) => !keys.has(key)),
|
|
201
|
+
remainingFindingKeys: [...keys]
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return history;
|
|
205
|
+
}
|
|
206
|
+
function runVerdictGate(input) {
|
|
207
|
+
const rounds = collectRounds(input.comments);
|
|
208
|
+
if (rounds.length === 0)
|
|
209
|
+
return { kind: "no-review" };
|
|
210
|
+
const latest = rounds[rounds.length - 1];
|
|
211
|
+
if (input.roundAfterId !== void 0 && (latest.id === void 0 || latest.id <= input.roundAfterId)) {
|
|
212
|
+
return { kind: "no-round", commentBody: renderNoRoundNotice(input.locale, input.headSha) };
|
|
213
|
+
}
|
|
214
|
+
if (latest.extraction.status !== "ok") {
|
|
215
|
+
return { kind: "unparseable", commentBody: renderUnparseableNotice(input.locale, input.headSha) };
|
|
216
|
+
}
|
|
217
|
+
const verdict = computeVerdict(latest.extraction.findings);
|
|
218
|
+
const commentBody = renderVerdictComment({
|
|
219
|
+
verdict,
|
|
220
|
+
history: computeRoundHistory(rounds),
|
|
221
|
+
maxRounds: input.maxRounds,
|
|
222
|
+
locale: input.locale,
|
|
223
|
+
isFinalRound: rounds.length >= input.maxRounds,
|
|
224
|
+
headSha: input.headSha
|
|
225
|
+
});
|
|
226
|
+
return { kind: "verdict", verdict, commentBody, exitCode: verdict === "BLOCK" ? 1 : 0 };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/bin/review-verdict.ts
|
|
230
|
+
var realExec = (command, args, stdin) => {
|
|
231
|
+
const result = spawnSync(command, args, { encoding: "utf-8", input: stdin });
|
|
232
|
+
return { status: result.status ?? null, stdout: result.stdout ?? "" };
|
|
233
|
+
};
|
|
234
|
+
function runReviewVerdictBin(env, exec) {
|
|
235
|
+
const logs = [];
|
|
236
|
+
const repo = env["GITHUB_REPOSITORY"];
|
|
237
|
+
const pr = env["ARCHWEAVE_PR_NUMBER"];
|
|
238
|
+
const maxRounds = Number(env["ARCHWEAVE_MAX_ROUNDS"]);
|
|
239
|
+
const locale = env["ARCHWEAVE_LOCALE"] === "ko" ? "ko" : "en";
|
|
240
|
+
if (!repo || !pr || !Number.isInteger(maxRounds) || maxRounds < 1) {
|
|
241
|
+
logs.push("verdict gate: required environment missing or malformed \u2014 skipping (check stays green).");
|
|
242
|
+
return { exitCode: 0, logs };
|
|
243
|
+
}
|
|
244
|
+
const watermark = env["ARCHWEAVE_ROUND_AFTER_ID"];
|
|
245
|
+
const roundAfterId = watermark !== void 0 && /^\d+$/.test(watermark) ? Number(watermark) : void 0;
|
|
246
|
+
const sha = env["ARCHWEAVE_HEAD_SHA"];
|
|
247
|
+
const headSha = sha !== void 0 && /^[0-9a-f]{7,40}$/i.test(sha) ? sha : void 0;
|
|
248
|
+
const fetched = exec("gh", ["api", `repos/${repo}/issues/${pr}/comments`, "--paginate"]);
|
|
249
|
+
if (fetched.status !== 0) {
|
|
250
|
+
logs.push("verdict gate: could not fetch PR comments via gh \u2014 skipping (check stays green).");
|
|
251
|
+
return { exitCode: 0, logs };
|
|
252
|
+
}
|
|
253
|
+
const pages = splitConcatenatedJson(fetched.stdout);
|
|
254
|
+
if (pages === null) {
|
|
255
|
+
logs.push("verdict gate: unexpected gh output \u2014 skipping (check stays green).");
|
|
256
|
+
return { exitCode: 0, logs };
|
|
257
|
+
}
|
|
258
|
+
const comments = pages.flatMap((page) => Array.isArray(page) ? page : []).filter(
|
|
259
|
+
(c) => c !== null && typeof c === "object" && typeof c.body === "string" && c.user?.type === "Bot"
|
|
260
|
+
).map((c) => ({ body: c.body, id: typeof c.id === "number" ? c.id : void 0 }));
|
|
261
|
+
const result = runVerdictGate({ comments, maxRounds, locale, roundAfterId, headSha });
|
|
262
|
+
if (result.kind === "no-review") {
|
|
263
|
+
logs.push("verdict gate: no review rounds found on this PR \u2014 nothing to judge.");
|
|
264
|
+
return { exitCode: 0, logs };
|
|
265
|
+
}
|
|
266
|
+
const previous = [...comments].reverse().find((c) => c.body.includes(VERDICT_MARKER) && c.id !== void 0);
|
|
267
|
+
const payload = JSON.stringify({ body: result.commentBody });
|
|
268
|
+
const updated = previous !== void 0 && exec("gh", ["api", "-X", "PATCH", `repos/${repo}/issues/comments/${previous.id}`, "--input", "-"], payload).status === 0;
|
|
269
|
+
if (!updated) {
|
|
270
|
+
const post = exec("gh", ["api", "-X", "POST", `repos/${repo}/issues/${pr}/comments`, "--input", "-"], payload);
|
|
271
|
+
if (post.status !== 0) logs.push("verdict gate: could not post the verdict comment (continuing).");
|
|
272
|
+
}
|
|
273
|
+
if (result.kind === "no-round") {
|
|
274
|
+
logs.push("verdict gate: this run posted no review round for the current commit \u2014 earlier rounds are not judged again; check stays green.");
|
|
275
|
+
return { exitCode: 0, logs };
|
|
276
|
+
}
|
|
277
|
+
if (result.kind === "unparseable") {
|
|
278
|
+
logs.push("verdict gate: review output was not machine-readable \u2014 human review requested, check stays green.");
|
|
279
|
+
return { exitCode: 0, logs };
|
|
280
|
+
}
|
|
281
|
+
logs.push(`verdict gate: ${result.verdict}`);
|
|
282
|
+
return { exitCode: result.exitCode, logs };
|
|
283
|
+
}
|
|
284
|
+
function splitConcatenatedJson(stdout) {
|
|
285
|
+
const text = stdout.trim();
|
|
286
|
+
if (text === "") return [];
|
|
287
|
+
const values = [];
|
|
288
|
+
let depth = 0;
|
|
289
|
+
let start = 0;
|
|
290
|
+
let inString = false;
|
|
291
|
+
let escaped = false;
|
|
292
|
+
for (let i = 0; i < text.length; i++) {
|
|
293
|
+
const ch = text[i];
|
|
294
|
+
if (inString) {
|
|
295
|
+
if (escaped) escaped = false;
|
|
296
|
+
else if (ch === "\\") escaped = true;
|
|
297
|
+
else if (ch === '"') inString = false;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (ch === '"') inString = true;
|
|
301
|
+
else if (ch === "[" || ch === "{") depth++;
|
|
302
|
+
else if (ch === "]" || ch === "}") {
|
|
303
|
+
depth--;
|
|
304
|
+
if (depth === 0) {
|
|
305
|
+
try {
|
|
306
|
+
values.push(JSON.parse(text.slice(start, i + 1)));
|
|
307
|
+
} catch {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
start = i + 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (depth !== 0 || text.slice(start).trim() !== "") return null;
|
|
315
|
+
return values;
|
|
316
|
+
}
|
|
317
|
+
var invokedAsBin = false;
|
|
318
|
+
try {
|
|
319
|
+
invokedAsBin = process.argv[1] !== void 0 && realpathSync(process.argv[1]).endsWith("review-verdict.js");
|
|
320
|
+
} catch {
|
|
321
|
+
invokedAsBin = false;
|
|
322
|
+
}
|
|
323
|
+
if (invokedAsBin) {
|
|
324
|
+
const { exitCode, logs } = runReviewVerdictBin(process.env, realExec);
|
|
325
|
+
for (const line of logs) console.log(line);
|
|
326
|
+
process.exit(exitCode);
|
|
327
|
+
}
|
|
328
|
+
export {
|
|
329
|
+
runReviewVerdictBin
|
|
330
|
+
};
|