Skip to content

Commit 0f5daa0

Browse files
docs: fix small inconsistencies in the documentation (#12)
1 parent d503ddd commit 0f5daa0

File tree

8 files changed

+77
-61
lines changed

8 files changed

+77
-61
lines changed

.env.example

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ GITHUB_WEBHOOK_SECRET=
2828
# Set to true to enable verbose debug logging (git commands, file lists, API calls).
2929
# DEBUG_MODE=true
3030

31-
# Required when any repo in config/repos.json has claude.enabled: true.
31+
# Required when any repo in config/layne.json has claude.enabled: true.
3232
# ANTHROPIC_API_KEY=
3333

3434
# ── Notifications ─────────────────────────────────────────────────────────────
3535

3636
# Global Rocket.Chat incoming webhook URL.
37-
# Reference this in config/repos.json as "$ROCKETCHAT_WEBHOOK_URL".
37+
# Reference this in config/layne.json as "$ROCKETCHAT_WEBHOOK_URL".
3838
# ROCKETCHAT_WEBHOOK_URL=
3939

4040
# Add extra per-repo webhook variables here as needed, for example:

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Two separate Node.js processes:
8585
**Scanners (`src/adapters/`):**
8686
- `semgrep.js` — runs `semgrep scan --config auto --json`; exit code 1 = findings found (not an error); maps ERROR→high, WARNING→medium, INFO→low
8787
- `trufflehog.js` — runs `trufflehog filesystem --json --no-update`; exit code 183 = secrets found (not an error); batched at 200 files to stay under ARG_MAX; all findings are severity `high`
88-
- `claude.js` — calls the Anthropic API to detect malicious intent; **disabled by default**, opt in per repo; skips binary files; caps files at 50 KB; batches at 100 KB per API call; errors are caught and logged without failing the scan. Supports two modes (configured per-repo in `repos.json`):
88+
- `claude.js` — calls the Anthropic API to detect malicious intent; **disabled by default**, opt in per repo; skips binary files; caps files at 50 KB; batches at 100 KB per API call; errors are caught and logged without failing the scan. Supports two modes (configured per-repo in `config/layne.json`):
8989
- **Prompt mode** (default): single `messages.create` call with a system prompt; use `claude.prompt` to override
9090
- **Skill mode**: uses the Anthropic [API Skills beta](https://platform.claude.com/docs/en/build-with-claude/skills-guide) — adds a `code_execution` tool + an uploaded skill to each batch call, enabling runtime decoding, registry lookups, and richer static analysis; set `claude.skill: { id, version }` to enable; handles `pause_turn` continuations automatically (up to 10 turns per batch)
9191

docs/configuration.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ Uses the [Anthropic API Skills beta](https://platform.claude.com/docs/en/build-w
107107
| `id` | string || Skill ID from the Anthropic Skills API (format: `skill_01...`) |
108108
| `version` | string | `"latest"` | Skill version to use. Pin to a timestamp for reproducible behaviour |
109109

110-
> **Beta — expect breaking changes.** API Skills are in active development. The beta headers (`skills-2025-10-02`, `code-execution-2025-08-25`) may be superseded by Anthropic; when that happens, Layne will need to be updated to use the new headers before skill mode works again. Skill IDs (`skill_01...`) are opaque, tied to your Anthropic account, and are not portable — if Anthropic changes the Skills API in a way that invalidates existing uploads, you will need to re-upload your skill and update the `id` in `repos.json`. Skills are not ZDR-eligible. Use `claude-sonnet-4-6` or above — smaller models may not make effective use of code execution.
110+
> **Beta — expect breaking changes.** API Skills are in active development. The beta headers (`skills-2025-10-02`, `code-execution-2025-08-25`) may be superseded by Anthropic; when that happens, Layne will need to be updated to use the new headers before skill mode works again. Skill IDs (`skill_01...`) are opaque, tied to your Anthropic account, and are not portable — if Anthropic changes the Skills API in a way that invalidates existing uploads, you will need to re-upload your skill and update the `id` in `config/layne.json`.
111+
>
112+
> **Skills are not ZDR-eligible.** ZDR (Zero Data Retention) is an Anthropic compliance feature that guarantees prompts and outputs are not retained after the API call. Skills require data retention to function, so they cannot be used with ZDR-enabled Anthropic organizations. Use `claude-sonnet-4-6` or above — smaller models may not make effective use of code execution.
111113
112114
**Uploading a skill:** Skills are managed outside of Layne. To upload one:
113115
```python
@@ -483,7 +485,7 @@ Sends a POST request to a Rocket.Chat incoming webhook URL.
483485
| `webhookUrl` | string | yes | Webhook URL, or an env var reference like `"$ROCKETCHAT_WEBHOOK_URL"` |
484486
| `template` | string | no | Custom message template (see below). Omit for the default format |
485487

486-
**`webhookUrl` — keeping secrets out of `repos.json`:**
488+
**`webhookUrl` — keeping secrets out of `config/layne.json`:**
487489

488490
If the value starts with `$`, Layne treats the rest as an environment variable name and reads it at runtime. This way your webhook URL never needs to be committed to the repository.
489491

@@ -540,7 +542,7 @@ Sends a POST request to a Slack incoming webhook URL.
540542

541543
Create a Slack app, enable Incoming Webhooks, and add a webhook for your channel. Copy the resulting `https://hooks.slack.com/services/...` URL.
542544

543-
**`webhookUrl` — keeping secrets out of `repos.json`:**
545+
**`webhookUrl` — keeping secrets out of `config/layne.json`:**
544546

545547
Same `$ENV_VAR` resolution as Rocket.Chat — if the value starts with `$`, Layne reads it from the environment at runtime.
546548

docs/extending.md

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function toFinding(result, workspacePath) {
4141
return {
4242
file, // repo-root-relative path (required)
4343
line: result.line ?? 1, // line number (required)
44-
severity: 'high', // 'high' | 'medium' | 'low'
44+
severity: 'high', // 'critical' | 'high' | 'medium' | 'low' | 'info'
4545
message: result.message, // annotation body text
4646
ruleId: `mytool/${result.id}`, // stable identifier for the rule
4747
tool: 'mytool', // used in the check run summary
@@ -67,7 +67,7 @@ function exec(cmd, args, options = {}) {
6767
|---|---|---|
6868
| `file` | `string` | Path relative to the repo root (strip `workspacePath + '/'`) |
6969
| `line` | `number` | Line number for the annotation (use `1` if unavailable) |
70-
| `severity` | `'critical' \| 'high' \| 'medium' \| 'low'` | Controls annotation styling in the GitHub UI |
70+
| `severity` | `'critical' \| 'high' \| 'medium' \| 'low' \| 'info'` | Controls annotation styling and whether the check fails |
7171
| `message` | `string` | Body text of the inline annotation |
7272
| `ruleId` | `string` | Stable identifier used to deduplicate or suppress findings |
7373
| `tool` | `string` | Name shown in the check run summary |
@@ -110,56 +110,77 @@ Pin the version so builds are reproducible. Pass `--build-arg MYTOOL_VERSION=x.y
110110
111111
---
112112
113-
## Adding a New Notification Provider
113+
## How Findings Become GitHub Annotations
114114
115-
Notifications are **modular**: each provider is an independent file in `src/notifiers/`. Adding a new provider (e.g. PagerDuty) requires three steps and no changes to core scan logic.
115+
Adapters return findings — they don't call the reporter directly. Understanding this flow helps when debugging or adding new scanners:
116116
117-
### 1. Write the notifier
117+
```
118+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
119+
│ Scanner A │ │ Scanner B │ │ Scanner C │ │ ...
120+
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
121+
│ │ │ │
122+
│ findings[] │ findings[] │ findings[] │
123+
└───────────────────┴───────────────────┴───────────────────┘
124+
125+
126+
┌─────────────┐
127+
│ dispatcher │ (src/dispatcher.js)
128+
└──────┬──────┘
129+
130+
│ merged findings[]
131+
132+
┌─────────────┐
133+
│ reporter │ (src/reporter.js)
134+
└──────┬──────┘
135+
136+
│ { annotations, conclusion, summary }
137+
138+
┌─────────────┐
139+
│ GitHub API │ (Check Runs)
140+
└─────────────┘
141+
```
118142
119-
Create `src/notifiers/pagerduty.js` exporting a `notify` function. The function must **never throw** — catch all errors internally so a notification failure never affects the scan result.
143+
**What the dispatcher does:**
120144
121-
```js
122-
// src/notifiers/pagerduty.js
145+
1. Runs all scanners in parallel via `Promise.all`
146+
2. Merges all findings into a single array
147+
3. Returns the merged array to the worker
123148
124-
export async function notify({ findings, owner, repo, prNumber, toolConfig }) {
125-
const url = toolConfig.webhookUrl;
126-
if (!url) return;
149+
**What the reporter does:**
127150
128-
try {
129-
const res = await fetch(url, {
130-
method: 'POST',
131-
headers: { 'Content-Type': 'application/json' },
132-
body: JSON.stringify({
133-
summary: `${findings.length} finding(s) in ${owner}/${repo} PR #${prNumber}`,
134-
}),
135-
});
136-
if (!res.ok) {
137-
console.error(`[pagerduty] notification failed: HTTP ${res.status}`);
138-
}
139-
} catch (err) {
140-
console.error(`[pagerduty] notification failed: ${err.message}`);
141-
}
142-
}
143-
```
151+
The reporter (`src/reporter.js`) receives the merged findings array and produces GitHub Check Run output:
144152
145-
### 2. Register the notifier in the orchestrator
153+
### Severity mapping
146154
147-
Open `src/notifiers/index.js` and add two lines:
155+
GitHub Check Runs support three annotation levels: `failure`, `warning`, and `notice`.
148156
149-
```js
150-
import { notify as notifyRocketchat } from './rocketchat.js';
151-
import { notify as notifySlack } from './slack.js';
152-
import { notify as notifyPagerduty } from './pagerduty.js'; // add this
153-
154-
const NOTIFIERS = {
155-
rocketchat: notifyRocketchat,
156-
slack: notifySlack,
157-
pagerduty: notifyPagerduty, // add this
158-
};
159-
```
157+
| Finding severity | GitHub level | Merge blocked? |
158+
|---|---|---|
159+
| `critical` | `failure` | Yes — branch protection will block merge |
160+
| `high` | `failure` | Yes — branch protection will block merge |
161+
| `medium` | `warning` | No — visible in PR files tab, yellow marker |
162+
| `low` | `notice` | No — informational, minimal visibility |
163+
| `info` | `notice` | No — informational |
164+
165+
### Check Run conclusion
166+
167+
The overall Check Run conclusion determines whether GitHub shows a green check or red ✗:
168+
169+
| Condition | Conclusion |
170+
|---|---|
171+
| One or more `critical` / `high` findings | `failure` |
172+
| No blocking findings | `success` |
173+
174+
When branch protection requires the Layne check, `failure` blocks the PR from merging.
160175
161-
The notifier key (`pagerduty`) is what operators use in `config/layne.json` under `notifications`.
176+
### Annotation summary
177+
178+
The reporter generates a human-readable summary line shown in the Check Run header:
179+
180+
```
181+
Found 3 issue(s): 0 critical, 1 high, 1 medium, 1 low.
182+
```
162183
163-
### 3. Write tests
184+
### Annotation chunking
164185
165-
Create `src/__tests__/notifiers/pagerduty.test.js` following the same pattern as the Rocket.Chat or Slack test files. Use `vi.stubGlobal('fetch', vi.fn())` to mock HTTP calls.
186+
GitHub's API limits Check Runs to 50 annotations per request. The reporter batches automatically — adapters don't need to worry about this limit. The worker posts chunked requests to GitHub, with the final request setting `status: completed`.

docs/local-development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ After installation, look at the URL of the page you land on:
9393
https://github.com/settings/installations/NNNNNNNN
9494
```
9595

96-
Note that number — it is your **installation ID**. You will need it to update the fixture files later.
96+
**Installation ID:** The number in the URL (`NNNNNNNN`) is your installation ID. You will need it to update the fixture files later.
9797

9898
---
9999

docs/reference.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,15 @@ All scanners produce findings in a common format:
2424
```js
2525
{
2626
file: 'src/app.js', // repo-root-relative path
27+
severity: 'high', // 'critical' | 'high' | 'medium' | 'low' | 'info'
2728
line: 42, // line number
28-
severity: 'high', // 'critical' | 'high' | 'medium' | 'low'
2929
message: 'SQL injection', // annotation body text
3030
ruleId: 'semgrep/rule-id', // stable rule identifier
3131
tool: 'semgrep', // scanner name
3232
}
3333
```
3434

35-
## Severity → GitHub annotation level
36-
37-
| Severity | GitHub level | Effect |
38-
|---|---|---|
39-
| `critical` | `failure` | Blocks merge |
40-
| `high` | `failure` | Blocks merge |
41-
| `medium` | `warning` | Visible warning |
42-
| `low` | `notice` | Informational |
35+
For how findings are converted to GitHub annotations and how severities affect PR status, see [Extending Layne — How Findings Become GitHub Annotations](extending.md#how-findings-become-github-annotations).
4336

4437
## Scan timeout
4538

docs/security-architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Used only when a repo has `claude.enabled: true`. The key is passed directly to
4141

4242
### Notification webhook URLs (`webhookUrl`)
4343

44-
Rocket.Chat webhook URLs can be stored as environment variable references (e.g. `"$ROCKETCHAT_WEBHOOK_URL"`) in `repos.json` rather than as plaintext values. Layne resolves them at runtime from `process.env`. This keeps secrets out of the repository.
44+
Rocket.Chat webhook URLs can be stored as environment variable references (e.g. `"$ROCKETCHAT_WEBHOOK_URL"`) in `config/layne.json` rather than as plaintext values. Layne resolves them at runtime from `process.env`. This keeps secrets out of the repository.
4545

4646
---
4747

src/adapters/claude.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ const REPORT_FINDINGS_TOOL = {
6161
export async function runClaude({ workspacePath, changedFiles, toolConfig = DEFAULT_CONFIG.claude }) {
6262
if (!changedFiles || changedFiles.length === 0) return [];
6363
if (!toolConfig.enabled) {
64-
console.log('[claude] skipping — not enabled for this repo (set "claude": {"enabled": true} in config/repos.json)');
64+
console.log('[claude] skipping — not enabled for this repo (set "claude": {"enabled": true} in config/layne.json)');
6565
return [];
6666
}
6767

0 commit comments

Comments
 (0)