pm-github 2026.10.4 → 2026.10.8
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/CHANGELOG.md +16 -0
- package/README.md +94 -2
- package/dist/gate.d.ts +269 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +873 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +126 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +511 -180
- package/dist/index.js.map +1 -1
- package/dist/projects.d.ts +11 -0
- package/dist/projects.d.ts.map +1 -1
- package/dist/projects.js +17 -7
- package/dist/projects.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +25 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2026.10.8 - 2026-10-08
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Fail-closed privacy gate for automated GitHub issue imports and one reusable sync workflow ([pm-github-f1at](https://github.com/unbraind/pm-github/blob/main/.agents/pm/features/pm-github-f1at.toon))
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Bound native Bun CLI repeats of a completed public atomic import ([pm-github-hptv](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-hptv.toon))
|
|
12
|
+
- Root CI runs skip the read-only-directory comment-sync fixtures visibly instead of failing them ([pm-github-rap0](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-rap0.toon))
|
|
13
|
+
|
|
14
|
+
### Other
|
|
15
|
+
|
|
16
|
+
- Reach exact all-source coverage and include every operational script ([pm-github-9cjx](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-9cjx.toon))
|
|
17
|
+
- Auto-merge green Dependabot updates and group the pm toolchain into one daily PR ([pm-github-u9df](https://github.com/unbraind/pm-github/blob/main/.agents/pm/tasks/pm-github-u9df.toon))
|
|
18
|
+
|
|
3
19
|
## 2026.9.26 - 2026-09-26
|
|
4
20
|
|
|
5
21
|
### Other
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ pm install npm:pm-github --global
|
|
|
23
23
|
|---|---|
|
|
24
24
|
| `importers` | `pm github import <owner/repo>` — idempotent native import pipeline |
|
|
25
25
|
| `importers` (exporter) | `pm github export` — pm items → GitHub issues (dry-run by default; upsert) |
|
|
26
|
-
| `commands` | `pm gh-issues import` (legacy import alias), `pm github sync` (push status), `pm github validate` (diagnostics), `pm github project list\|fields\|import\|sync` (Projects v2) |
|
|
26
|
+
| `commands` | `pm gh-issues import` (legacy import alias), `pm github sync` (push status), `pm github gate` (fail-closed pre-push privacy gate), `pm github validate` (diagnostics), `pm github project list\|fields\|import\|sync` (Projects v2) |
|
|
27
27
|
| `schema` | declares `github_url`, `github_number`, `github_state`, `github_author`, `github_created_at`, `github_updated_at` item fields |
|
|
28
28
|
| `hooks` | `afterCommand` — opt-in sync reminder (`PM_GITHUB_SYNC`) for linked items |
|
|
29
29
|
| `preflight` | early warning when a mutating github command lacks a token |
|
|
@@ -91,6 +91,7 @@ pm github import owner/repo --dry-run
|
|
|
91
91
|
| `--comments-mode <mode>` | `body`\|`annotations`\|`both` | How fetched GitHub comments are persisted (default `body`). `annotations` syncs comments into the pm item's native comments collection via the SDK; `both` writes the body section AND native comments. `annotations`/`both` are idempotent on re-import (dedupe by GitHub comment id) |
|
|
92
92
|
| `--atomic` | boolean | Commit every create, update, close, and reopen in one workspace-writer-locked, crash-resumable transaction (requires pm CLI/SDK >=2026.7.20). Normal failure compensation restores updated/closed items and deletes newly created items; an incomplete compensation is reported explicitly for retry or repair. |
|
|
93
93
|
| `--link-deps` | boolean | After import, map dependency references in issue **bodies** (`Blocked by #N`, `Depends on owner/repo#N`, `Blocks #N`) to pm dependency edges between the linked items. Idempotent, best-effort, and re-runnable; see [Dependency linking](#dependency-linking---link-deps) below. |
|
|
94
|
+
| `--gate` | boolean | Fail-closed privacy gate: verify plan completeness (every fetched issue accounted for exactly once) and provenance idempotency (every re-import lands on exactly one `gh:owner/repo#N`-tagged item) BEFORE the write, then scan the proposed tracker change with [`pm github gate`](#pm-github-gate) AFTER it. Any finding — or any unreadable scan input — exits non-zero so an automated pipeline can never commit or push the change. Embeds `gate` receipts in the result. |
|
|
94
95
|
| `--dry-run` | boolean | Preview without writing |
|
|
95
96
|
| `--type <type>` | string | Override pm item type (default: Issue) |
|
|
96
97
|
|
|
@@ -248,6 +249,95 @@ pm search "uppercase dashes" --semantic # hits = imported items whose ups
|
|
|
248
249
|
|
|
249
250
|
Enable it by setting `search.provider` to `"github"` in `.agents/pm/settings.json` and pointing it at a repo via the `PM_GITHUB_REPO` env var.
|
|
250
251
|
|
|
252
|
+
## Privacy gate
|
|
253
|
+
|
|
254
|
+
### `pm github gate`
|
|
255
|
+
|
|
256
|
+
GitHub issue text is untrusted: anyone who can open an issue can put a credential, a personal email, or a host path in it. An automated import writes that text into the pm tracker, and a sync pipeline then pushes the tracker to a public branch — publishing the leak with it. `pm github gate` is the fail-closed step between the write and any push:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
pm github gate # scan the proposed tracker change in the working tree
|
|
260
|
+
pm github gate --json # machine-readable report (also via the global --json)
|
|
261
|
+
pm github gate --diff sync.patch # scan an explicit unified diff file instead
|
|
262
|
+
pm github gate --allowlist .pm-github-gate-allowlist.json
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
It scans **only the proposed change** — the added lines and their filenames in the staged/working/untracked diff under the resolved pm tracker path (never a hardcoded `.agents/pm`; the SDK-resolved path wins), or an explicit `--diff` file — so pre-existing reviewed content is not re-litigated on every run, and removed content (which cannot publish anything new) is never scanned. A finding in a filename uses `field: "file_path"` and redacts a sensitive item id. It fails closed (non-zero, machine-readable report with `item_id` + `field` + `rule` + the sha256 `hash` of the matched content, **never the matched content itself**) on:
|
|
266
|
+
|
|
267
|
+
- **credentials** — GitHub (`ghp_/gho_/ghu_/ghs_/ghr_` and fine-grained `github_pat_`), npm, AWS access key ids, Slack (including app and cookie credentials), OpenAI, Anthropic tokens, generic `Authorization: Bearer` values, `-----BEGIN … PRIVATE KEY-----` blocks, and high-entropy assignments to secret-named identifiers (`token = "…"`, `api_key: …`, `accessToken: …`) whose value has high measured entropy. UUID-shaped credential values and values containing dates receive the same checks;
|
|
268
|
+
- **personal data** — email addresses other than no-reply identities (`@users.noreply.github.com`, `noreply@…`, `@noreply.…`), phone numbers in international (`+…`) or North-American (`(555) 123-4567`, `555-123-4567`) notation, and unformatted numbers under phone/tel/mobile contact labels;
|
|
269
|
+
- **host paths** — absolute local filesystem paths (POSIX home/system directories and Windows drive paths with either separator), and named home-directory references.
|
|
270
|
+
|
|
271
|
+
Unreadable input, malformed or truncated diffs, binary input, invalid UTF-8, Git failures, malformed allowlists, and scanner errors fail the gate. Untracked operational state in the tracker root (`locks/`, `extensions/`, `checkpoints/`) is excluded. Explicitly staged operational files remain in scope. Staged and unstaged changes are scanned separately, including staged content that the working copy subsequently removed.
|
|
272
|
+
|
|
273
|
+
**False positives** are allowlisted by *content hash*, not by pattern: put the sha256 from the finding into `.pm-github-gate-allowlist.json` at the repository root (or pass `--allowlist <file>`) with a written justification. An entry suppresses exactly the reviewed content, can never widen to a pattern, and a missing justification fails the gate:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"<sha256 of the reviewed finding content>": { "reason": "Reviewed: public support address in the import docs item." }
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### `pm github import --gate`
|
|
282
|
+
|
|
283
|
+
`--gate` composes the gate into the import pipeline, in the order a sync workflow needs:
|
|
284
|
+
|
|
285
|
+
1. **dry-run plan** — every fetched issue is prepared up front;
|
|
286
|
+
2. **completeness check** — every fetched issue is accounted for exactly once (planned or explicitly skipped), nothing outside the fetch appears, counts reconcile — all before any mutation;
|
|
287
|
+
3. **idempotency check** — no duplicate `gh:owner/repo#N` provenance tags in the existing corpus, every new entry born with its tag, no two plan entries resolving to the same item — so a second run reuses the existing item;
|
|
288
|
+
4. **write** — the ordinary (atomic or per-item) import;
|
|
289
|
+
5. **gate** — the proposed tracker change is scanned; a finding exits non-zero, so the caller never commits or pushes.
|
|
290
|
+
|
|
291
|
+
Rendered values are also scanned in memory before mutation, so a known finding cannot be written to disk. After mutation, the importer verifies that every planned provenance tag exists exactly once, then scans the tracker diff. A completed SDK journal cannot substitute for missing item files.
|
|
292
|
+
|
|
293
|
+
The `gate` receipt reports the verdict, scanned file count, findings, and allowlisted count. With `--json`, successful reports go to stdout; failures use the host CLI's JSON error envelope on stderr, with the redacted gate report serialized in `detail` (`jq ' .detail | fromjson ' gate-error.json`). Unreadable inputs produce a nonzero JSON refusal.
|
|
294
|
+
|
|
295
|
+
## Automated sync workflow
|
|
296
|
+
|
|
297
|
+
Fleet repos sync their issues into pm through ONE audited implementation: the reusable `workflow_call` workflow at [`.github/workflows/pm-github-sync.yml`](.github/workflows/pm-github-sync.yml) in this repository. It runs the gated pipeline in fail-closed order — checkout → install the **pinned** extension → `pm github validate` → dry-run plan → **gated import** → verify the repeat import is a no-op → `pm health --strict-exit` → and only then commit, push the sync branch, and open/update a review PR whose body links every changed item as `https://github.com/<repo>/blob/main/.agents/pm/<folder>/<id>.toon`. Because `--gate` exits non-zero on any finding, the job can never reach the push step with a leak in the change. Actions are pinned by commit SHA; permissions are least-privilege (`contents: write`, `pull-requests: write`, `issues: read`).
|
|
298
|
+
|
|
299
|
+
A fleet repo owns the schedule and calls it with the pinned versions (see [`docs/sync-workflow-caller.yml`](docs/sync-workflow-caller.yml)):
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
name: Sync GitHub issues into pm
|
|
303
|
+
on:
|
|
304
|
+
schedule:
|
|
305
|
+
- cron: "17 2 * * *"
|
|
306
|
+
workflow_dispatch:
|
|
307
|
+
|
|
308
|
+
permissions:
|
|
309
|
+
contents: write
|
|
310
|
+
pull-requests: write
|
|
311
|
+
issues: read
|
|
312
|
+
|
|
313
|
+
jobs:
|
|
314
|
+
gated-sync:
|
|
315
|
+
uses: unbraind/pm-github/.github/workflows/pm-github-sync.yml@RELEASE_COMMIT_SHA
|
|
316
|
+
with:
|
|
317
|
+
pm-github-version: "RELEASE_VERSION" # required: exact published version
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Replace the two release placeholders after the gated extension is published; the current published version does not contain this feature. The calling repo needs `@unbrained/pm-cli` as a devDependency (the workflow's `npm ci` provides the CLI) and its pm tracker at `.agents/pm` committed on `main`. Under **Settings → Actions → General → Workflow permissions** it must also allow read and write token permissions and enable **Allow GitHub Actions to create and approve pull requests** (an organization can disable this), or the run pushes the sync branch and then fails to open the review PR. A PR that `GITHUB_TOKEN` opens or edits does not trigger `pull_request` workflows, so the sync PR gets no CI checks until a maintainer re-runs them (for example by closing and reopening the PR); the reusable workflow always uses the job's `GITHUB_TOKEN`.
|
|
321
|
+
|
|
322
|
+
### Installed CLI timeout acceptance
|
|
323
|
+
|
|
324
|
+
`node test/helpers/public-acceptance.ts` packs the candidate, installs it beside
|
|
325
|
+
its pinned host CLI in a disposable Git project, retrieves a paginated public
|
|
326
|
+
issue/comment snapshot, and replays the unchanged responses through the installed
|
|
327
|
+
HTTP client. It verifies a nonempty gated Node import, byte-identical Node and
|
|
328
|
+
three native Bun repeats, and strict health. No remote is configured.
|
|
329
|
+
|
|
330
|
+
Each import retains the original 45-second execution deadline. The acceptance
|
|
331
|
+
watchdog sends SIGTERM to the process group, then SIGKILL after a five-second
|
|
332
|
+
cleanup window; any timeout fails acceptance even if the child later exits zero.
|
|
333
|
+
A real spinning-child negative control verifies forced cleanup and unchanged
|
|
334
|
+
fixture bytes. The HTTP client also enforces a 30-second wall-clock deadline through redirect
|
|
335
|
+
and response-body completion, even when native socket timeouts do not fire.
|
|
336
|
+
The comment-lock retry honors its existing wait budget when
|
|
337
|
+
an exclusive-create collision cannot be statted, including a dangling symlink.
|
|
338
|
+
This reproduced lock defect is separate from the intermittent historical Bun
|
|
339
|
+
repeat: recent packed repeats pass, but its original cause is not established.
|
|
340
|
+
|
|
251
341
|
## Validate / diagnostics
|
|
252
342
|
|
|
253
343
|
### `pm github validate`
|
|
@@ -266,7 +356,9 @@ MIT
|
|
|
266
356
|
|
|
267
357
|
## Release Automation
|
|
268
358
|
|
|
269
|
-
|
|
359
|
+
Release checks require type checking, docstrings, ESLint, zero source duplication, exact 100/100/100/100 coverage across authored TypeScript and JavaScript modules including operational scripts, production dependency audit, package packing, Bun behavior, strict PM health, and pm-changelog validation. Unloaded modules count at zero; no source ignores or lowered thresholds are accepted. Coverage shortfalls block release. The shell changelog-date verifier is exercised separately and is outside the V8 percentage denominator. The daily release workflow publishes only when commits exist after the latest release tag and uses pm-changelog to generate CHANGELOG.md and GitHub release notes.
|
|
360
|
+
|
|
361
|
+
`npm run changelog:full` reads the complete tracker through the SDK and feeds pm-changelog. Closed work supplies release history; the explicit `changelog-unreleased` tag includes a pending candidate without closing its PM item. Other open work stays excluded.
|
|
270
362
|
|
|
271
363
|
## Multi-agent merge safety
|
|
272
364
|
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One fail-closed finding produced by the gate.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately carries no matched text: the report is machine-readable and may
|
|
5
|
+
* be echoed into CI logs, so it identifies WHAT fired (rule), WHERE (item id and
|
|
6
|
+
* the item field that would publish it), and the content-addressed allowlist
|
|
7
|
+
* key (hash) — nothing more.
|
|
8
|
+
*/
|
|
9
|
+
export interface GateFinding {
|
|
10
|
+
/** Stable rule identifier, safe to print (never contains matched content). */
|
|
11
|
+
readonly rule: string;
|
|
12
|
+
/** The pm item whose change carries the finding, or `""` when unattributable. */
|
|
13
|
+
readonly item_id: string;
|
|
14
|
+
/** The item field the offending added line belongs to, or `""` when unknown. */
|
|
15
|
+
readonly field: string;
|
|
16
|
+
/** sha256 hex digest of the matched content — the allowlist key. */
|
|
17
|
+
readonly hash: string;
|
|
18
|
+
}
|
|
19
|
+
/** Verdict of one gate run: `fail` whenever any non-allowlisted finding exists. */
|
|
20
|
+
export type GateVerdict = "pass" | "fail";
|
|
21
|
+
/** Machine-readable result of one gate run over a proposed tracker change. */
|
|
22
|
+
export interface GateReport {
|
|
23
|
+
/** `fail` when any finding survived the allowlist; `pass` otherwise. */
|
|
24
|
+
readonly verdict: GateVerdict;
|
|
25
|
+
/** Where the scanned change came from: `git` (working tree) or `diff` (explicit file). */
|
|
26
|
+
readonly source: "git" | "diff";
|
|
27
|
+
/** Number of changed tracker files whose added lines were scanned. */
|
|
28
|
+
readonly scanned_files: number;
|
|
29
|
+
/** Number of added lines scanned. */
|
|
30
|
+
readonly added_lines: number;
|
|
31
|
+
/** Findings that survived the allowlist, ordered file/line/ruleset. */
|
|
32
|
+
readonly findings: readonly GateFinding[];
|
|
33
|
+
/** Findings suppressed because their content hash was allowlisted. */
|
|
34
|
+
readonly allowlisted: number;
|
|
35
|
+
/** Path of the allowlist consulted (or `""` when none was resolved). */
|
|
36
|
+
readonly allowlist_path: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Input failure the gate treats as fail-closed: unreadable diff file, not a Git
|
|
40
|
+
* work tree, a Git command that failed, a malformed allowlist, or a scanner
|
|
41
|
+
* error. Callers translate this into a non-zero exit; it must never surface as a
|
|
42
|
+
* clean pass.
|
|
43
|
+
*/
|
|
44
|
+
export declare class GateInputError extends Error {
|
|
45
|
+
constructor(message: string);
|
|
46
|
+
}
|
|
47
|
+
/** One added line of a proposed tracker change, with its attributed item field. */
|
|
48
|
+
interface AddedLine {
|
|
49
|
+
/** The added text with the leading diff `+` already stripped. */
|
|
50
|
+
readonly text: string;
|
|
51
|
+
/** Best-effort item field the line belongs to (top-level toon key, or `""`). */
|
|
52
|
+
readonly field: string;
|
|
53
|
+
}
|
|
54
|
+
/** The proposed change to one tracker file: its repo-relative path plus added lines. */
|
|
55
|
+
interface ChangeFile {
|
|
56
|
+
/** Repo-relative or diff-relative path of the changed file. */
|
|
57
|
+
readonly filePath: string;
|
|
58
|
+
/** Best-effort pm item id for the file (`.toon` basename, embedded jsonl id, …). */
|
|
59
|
+
readonly itemId: string;
|
|
60
|
+
/** The added lines of the proposed change for this file. */
|
|
61
|
+
readonly addedLines: readonly AddedLine[];
|
|
62
|
+
}
|
|
63
|
+
/** Subprocess contract for running Git, injectable so tests can force failures. */
|
|
64
|
+
export type RunGit = (cwd: string, args: readonly string[]) => {
|
|
65
|
+
ok: boolean;
|
|
66
|
+
stdout: string;
|
|
67
|
+
stderr: string;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Collaborators a gate run depends on, injectable for tests.
|
|
71
|
+
*
|
|
72
|
+
* The production object shells out to Git and reads the filesystem; a test can
|
|
73
|
+
* substitute either to exercise the fail-closed paths deterministically (a Git
|
|
74
|
+
* failure, an unreadable file) without depending on machine state.
|
|
75
|
+
*/
|
|
76
|
+
export interface GateRunDependencies {
|
|
77
|
+
/** Git runner used to resolve the work tree and collect the change. */
|
|
78
|
+
readonly runGit?: RunGit;
|
|
79
|
+
/** File reader used for the explicit diff file and untracked tracker files. */
|
|
80
|
+
readonly readFileSync?: (filePath: string) => string;
|
|
81
|
+
}
|
|
82
|
+
/** Resolved input for one gate run: what to scan and which allowlist to consult. */
|
|
83
|
+
export interface TrackerGateInput {
|
|
84
|
+
/** pm root as the extension host supplies it (workspace root or data dir). */
|
|
85
|
+
readonly pmRoot: string;
|
|
86
|
+
/** Explicit unified diff file to scan instead of the working tree. */
|
|
87
|
+
readonly diffFile?: string;
|
|
88
|
+
/** Explicit allowlist file; missing explicit files fail closed. */
|
|
89
|
+
readonly allowlistFile?: string;
|
|
90
|
+
/** Injectable collaborators; production defaults shell out to Git and `fs`. */
|
|
91
|
+
readonly dependencies?: GateRunDependencies;
|
|
92
|
+
/** Rendered import values scanned in memory before persistence or diagnostics. */
|
|
93
|
+
readonly plannedItems?: readonly {
|
|
94
|
+
readonly itemId: string;
|
|
95
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
96
|
+
}[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolve the pm data dir from the `pmRoot` a command handler receives.
|
|
100
|
+
*
|
|
101
|
+
* The host may hand either the workspace root (the dir containing `.agents/pm`)
|
|
102
|
+
* or the data dir itself — the pm CLI accepts both for `--path`. This returns
|
|
103
|
+
* the dir that actually holds `settings.json` and `locks/`, defaulting to
|
|
104
|
+
* `pmRoot` unchanged when no nested `.agents/pm` exists.
|
|
105
|
+
*
|
|
106
|
+
* @param pmRoot - The path supplied by the extension host.
|
|
107
|
+
* @returns The resolved pm data directory.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolvePmDataDir(pmRoot: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* Shannon entropy per character of a value.
|
|
112
|
+
*
|
|
113
|
+
* Uniform random base62 has an expected ~5.5 bits/char, random hex ~4.0; natural
|
|
114
|
+
* language and dates sit far lower (~3–3.4). The assignment rule uses the gap:
|
|
115
|
+
* a long value over a secret-bearing identifier is only a finding when its
|
|
116
|
+
* per-character entropy is indistinguishable from random.
|
|
117
|
+
*
|
|
118
|
+
* @param value - The string to measure.
|
|
119
|
+
* @returns Bits per character (0 for the empty string).
|
|
120
|
+
*/
|
|
121
|
+
export declare function shannonEntropyPerChar(value: string): number;
|
|
122
|
+
/**
|
|
123
|
+
* Whether an `identifier = "value"` assignment looks like a hardcoded secret.
|
|
124
|
+
*
|
|
125
|
+
* Fires only when the identifier names a credential (token/secret/password/
|
|
126
|
+
* credential/api_key/access_key/private_key segment) AND the value is long
|
|
127
|
+
* (≥20 chars), high-entropy (≥3.9 bits/char — above dates, timestamps, prose),
|
|
128
|
+
* and the identifier names a credential. Both halves are required: identifier-only would
|
|
129
|
+
* flag every `token: "the token above"` prose reference, entropy-only would
|
|
130
|
+
* flag every generated id.
|
|
131
|
+
*
|
|
132
|
+
* @param identifier - The assignment target, e.g. `AWS_SECRET_ACCESS_KEY`.
|
|
133
|
+
* @param value - The assigned value text.
|
|
134
|
+
* @returns Whether the pair reads as a hardcoded credential.
|
|
135
|
+
*/
|
|
136
|
+
export declare function isHighEntropySecretAssignment(identifier: string, value: string): boolean;
|
|
137
|
+
/** One raw hit of a content rule, before item/field attribution. */
|
|
138
|
+
interface RuleHit {
|
|
139
|
+
/** Rule identifier that fired. */
|
|
140
|
+
readonly rule: string;
|
|
141
|
+
/** The matched text — kept in memory only; never printed or reported. */
|
|
142
|
+
readonly matched: string;
|
|
143
|
+
/** Character offset of the match within its line, for jsonl field attribution. */
|
|
144
|
+
readonly index: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Scan one added line with every content rule.
|
|
148
|
+
*
|
|
149
|
+
* Rules are evaluated independently: one line can produce several hits (a
|
|
150
|
+
* bearer header wrapping a GitHub token reports both shapes), because each is a
|
|
151
|
+
* distinct reviewed rule. The high-entropy assignment rule is regex-scan +
|
|
152
|
+
* predicate, so it is driven here rather than in the static table.
|
|
153
|
+
*
|
|
154
|
+
* @param line - The added line text (diff `+` already stripped).
|
|
155
|
+
* @returns Every rule hit on the line, in evaluation order.
|
|
156
|
+
*/
|
|
157
|
+
export declare function scanLineForRuleHits(line: string): RuleHit[];
|
|
158
|
+
/** One source line of a diff walk, marked added or unchanged context. */
|
|
159
|
+
interface WalkLine {
|
|
160
|
+
/** Line text without the diff marker. */
|
|
161
|
+
readonly text: string;
|
|
162
|
+
/** Whether the line is an added (`+`) line rather than context. */
|
|
163
|
+
readonly added: boolean;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Attribute a toon field to every line of a change walk.
|
|
167
|
+
*
|
|
168
|
+
* Walks the lines in file order maintaining the enclosing top-level field: a
|
|
169
|
+
* field header (added OR context) updates the tracker, so an added section row
|
|
170
|
+
* (`"ts",author,"text"`) attributes to its section (`notes`, `files`, …) even
|
|
171
|
+
* when the section header itself is unchanged context. Added lines that are
|
|
172
|
+
* their own field header attribute to that field.
|
|
173
|
+
*
|
|
174
|
+
* @param lines - Added and context lines in walk order.
|
|
175
|
+
* @returns A field name per input line (never `undefined`; `"unknown"` when no header was seen yet).
|
|
176
|
+
*/
|
|
177
|
+
export declare function attributeToonFields(lines: readonly WalkLine[]): string[];
|
|
178
|
+
/**
|
|
179
|
+
* Parse a unified diff into per-file added lines with toon field attribution.
|
|
180
|
+
*
|
|
181
|
+
* Accepts `git diff` (with or without `a/` `b/` prefixes) and plain
|
|
182
|
+
* `diff -u` output. Removed lines are skipped — only the proposed ADDITIONS can
|
|
183
|
+
* publish new content; deletions cannot leak anything that was not already in
|
|
184
|
+
* the tracked state.
|
|
185
|
+
*
|
|
186
|
+
* @param diffText - The unified diff text.
|
|
187
|
+
* @returns One entry per changed file with added lines and attribution.
|
|
188
|
+
*/
|
|
189
|
+
export declare function parseUnifiedDiff(diffText: string): ChangeFile[];
|
|
190
|
+
/**
|
|
191
|
+
* Run Git with the production subprocess runner.
|
|
192
|
+
*
|
|
193
|
+
* Trimmed stdout is returned as-is; a non-zero exit yields `ok: false` with the
|
|
194
|
+
* child's stderr so the fail-closed collector can report a diagnosable error.
|
|
195
|
+
*
|
|
196
|
+
* @param cwd - Directory to run in.
|
|
197
|
+
* @param args - Git argument vector.
|
|
198
|
+
* @returns The normalized subprocess result.
|
|
199
|
+
*/
|
|
200
|
+
export declare function runGitDefault(cwd: string, args: readonly string[]): {
|
|
201
|
+
ok: boolean;
|
|
202
|
+
stdout: string;
|
|
203
|
+
stderr: string;
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Collect the proposed tracker change from the Git working tree.
|
|
207
|
+
*
|
|
208
|
+
* The proposed change is everything a commit would publish: staged plus unstaged
|
|
209
|
+
* modifications of tracked files under the tracker path (their added diff lines)
|
|
210
|
+
* and the full content of untracked files under it. Unreachable/deleted paths
|
|
211
|
+
* contribute nothing; operational subpaths are excluded by
|
|
212
|
+
* {@link isOperationalTrackerPath}. Any Git or read failure throws
|
|
213
|
+
* {@link GateInputError} — fail closed, never pass vacuously.
|
|
214
|
+
*
|
|
215
|
+
* @param pmDataDir - The resolved pm data directory.
|
|
216
|
+
* @param dependencies - Injectable Git runner and file reader.
|
|
217
|
+
* @returns One change entry per scanned file (empty when the tree is clean).
|
|
218
|
+
*/
|
|
219
|
+
export declare function collectTrackerChange(pmDataDir: string, dependencies?: GateRunDependencies): ChangeFile[];
|
|
220
|
+
/** One reviewed allowlist entry for a matched content hash. */
|
|
221
|
+
interface AllowlistEntry {
|
|
222
|
+
/** Human-readable justification recorded next to the exemption in review. */
|
|
223
|
+
readonly reason: string;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Parse a gate allowlist file.
|
|
227
|
+
*
|
|
228
|
+
* The file maps the sha256 of REVIEWED matched content to a written
|
|
229
|
+
* justification. Entries without a non-empty reason are rejected — an unreviewed
|
|
230
|
+
* exemption is how a gate rots. A missing file fails closed only when it was
|
|
231
|
+
* passed explicitly; the default location is optional so a clean repo needs no
|
|
232
|
+
* allowlist file.
|
|
233
|
+
*
|
|
234
|
+
* @param allowlistPath - Absolute path of the allowlist JSON.
|
|
235
|
+
* @param required - Whether an explicit caller named the file (missing → error).
|
|
236
|
+
* @param readFileSync - File reader (injectable).
|
|
237
|
+
* @returns Hash → justification entries.
|
|
238
|
+
*/
|
|
239
|
+
export declare function readGateAllowlist(allowlistPath: string, required: boolean, readFileSync?: (filePath: string) => string): Map<string, AllowlistEntry>;
|
|
240
|
+
/** Default allowlist filename, resolved at the repository root when one exists. */
|
|
241
|
+
export declare const GATE_ALLOWLIST_FILENAME = ".pm-github-gate-allowlist.json";
|
|
242
|
+
/**
|
|
243
|
+
* Run the fail-closed privacy gate over a proposed tracker change.
|
|
244
|
+
*
|
|
245
|
+
* Resolves the change (explicit `--diff` file, or the staged/working/untracked
|
|
246
|
+
* diff under the resolved pm tracker path), scans every added line with the
|
|
247
|
+
* credential, personal-data, and host-path rules, and applies the reviewed
|
|
248
|
+
* content-hash allowlist. Any input, Git, allowlist, or scanner failure throws
|
|
249
|
+
* {@link GateInputError}; a returned report therefore always describes a
|
|
250
|
+
* completed scan.
|
|
251
|
+
*
|
|
252
|
+
* @param input - pm root, optional diff file, optional allowlist file, injectable deps.
|
|
253
|
+
* @returns The machine-readable gate report.
|
|
254
|
+
*/
|
|
255
|
+
export declare function runTrackerGate(input: TrackerGateInput): GateReport;
|
|
256
|
+
/**
|
|
257
|
+
* Render a gate report as human-readable lines.
|
|
258
|
+
*
|
|
259
|
+
* Every line names only the rule, item, field, and short hash prefix — the
|
|
260
|
+
* matched content never appears, so gate output is safe to paste anywhere. The
|
|
261
|
+
* same lines feed the command's stderr and the import failure message, so the
|
|
262
|
+
* two surfaces cannot drift apart.
|
|
263
|
+
*
|
|
264
|
+
* @param report - The gate report to render.
|
|
265
|
+
* @returns One summary line, then one line per finding.
|
|
266
|
+
*/
|
|
267
|
+
export declare function formatGateReport(report: GateReport): string[];
|
|
268
|
+
export {};
|
|
269
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../gate.ts"],"names":[],"mappings":"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"}
|