pullfrog 0.1.5 → 0.1.6
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/dist/agents/sessionLabeler.d.ts +38 -18
- package/dist/cli.mjs +294 -107
- package/dist/index.js +282 -95
- package/dist/internal.js +4 -2
- package/dist/utils/normalizeEnv.d.ts +21 -1
- package/dist/utils/subprocess.d.ts +40 -0
- package/dist/utils/timer.d.ts +11 -0
- package/package.json +1 -1
package/dist/internal.js
CHANGED
|
@@ -601,6 +601,8 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
601
601
|
- **4\u20135 lenses (high-stakes subsystem touches)** \u2014 any billing/payments change (billing-subsystem + correctness + security + operational-readiness); new auth flow (auth-subsystem + correctness + security + test-integrity); schema migration (schema-migration-subsystem + correctness + operational-readiness + impact); cross-subsystem PR that touches billing AND auth AND schema (one subsystem lens per domain + correctness)
|
|
602
602
|
- **6+ lenses** \u2014 almost always a smell; you're either covering overlapping ground or this PR should have been split. push back via the review body rather than expanding lens count.
|
|
603
603
|
|
|
604
|
+
**lens-add discipline.** Each lens needs to clear a specific bar before you dispatch it: name the concrete failure mode this lens would catch *that the diff plausibly introduces*, in one sentence. "Could apply", "good to have", "for completeness" do not qualify. If you can't name what the lens is going to find, drop it. The "when unsure, treat as non-trivial" rule above is for the trivial-vs-non-trivial gate at step 3 \u2014 it does not license expanding lens count without articulated risk. Every extra lens adds wall-time, log noise, and pulls subagent attention onto speculative angles, which biases the final review toward bloat-shaped findings.
|
|
605
|
+
|
|
604
606
|
lenses come in two flavors, and you can mix them:
|
|
605
607
|
- **themed lenses** \u2014 a perspective applied across the whole diff (correctness, security, user-journey, performance, etc.).
|
|
606
608
|
- **subsystem lenses** \u2014 a domain-scoped frame for high-stakes subsystems the PR touches (e.g. "the auth lens", "the billing lens", "the schema-migration lens"). a subsystem lens is "review the PR specifically for what could go wrong in this subsystem" and naturally combines theme + scope. **for high-stakes domains, lead with the subsystem lens rather than the generic themed equivalent** \u2014 "billing-subsystem" outperforms "correctness on billing code" because the framing primes the subagent to remember domain-specific failure modes (double-charges, refund races, currency rounding, dispute flows) the generic lens misses.
|
|
@@ -608,7 +610,7 @@ For simple, well-defined tasks, skip the plan phase and go straight to build.`
|
|
|
608
610
|
starter menu (combine, omit, or invent your own):
|
|
609
611
|
- **correctness & invariants** \u2014 bugs, races, error handling, edge cases, state-machine boundaries
|
|
610
612
|
- **impact** \u2014 when the PR removes features, deletes exports, renames identifiers, or changes architectural patterns: stale references in code, tests, docs (\`docs/\`, \`wiki/\`), comments, configs, UI
|
|
611
|
-
- **research-validated assumptions** \u2014 third-party API contracts, SDK semantics, framework directives, version-gated behavior. the subagent must verify load-bearing claims via web search and quote source URLs.
|
|
613
|
+
- **research-validated assumptions** \u2014 third-party API contracts, SDK semantics, framework directives, version-gated behavior. **only pick when the PR's correctness depends on the contract behaving a specific way** \u2014 not when the API is merely used. An idempotency key as a backstop, a timeout as a hint, a retry as belt-and-suspenders: not load-bearing, skip this lens. The bar is "if the third-party contract differs from what the diff assumes, the PR is incorrect." When dispatched, the subagent must verify load-bearing claims via web search and quote source URLs.
|
|
612
614
|
- **security** \u2014 new endpoints, authZ, input validation, secrets handling, replay/CSRF/injection, cross-tenant isolation
|
|
613
615
|
- **user-journey** \u2014 UX-touching flows: walk through happy path and failure modes as a user
|
|
614
616
|
- **operational readiness** \u2014 observability, alerting, migrations (forward + rollback), feature flags, on-call burden
|
|
@@ -697,7 +699,7 @@ ${PR_SUMMARY_FORMAT}`
|
|
|
697
699
|
"Looks trivial but isn't" (do NOT skip \u2014 same anti-patterns as Review mode): 1-line changes to SQL/regex/auth/billing/permissions/signature-verification code; flipping feature-flag defaults or retry/timeout constants; money/tax/HTTP-method/redirect changes; tightening or loosening a comparison operator; mixed diffs with a semantic line buried in formatting.
|
|
698
700
|
When unsure, treat as non-trivial.
|
|
699
701
|
|
|
700
|
-
otherwise pick lenses by where the new commits concentrate risk \u2014 **there's no fixed count**, same calibration as Review mode (1 lens for pure refactor / isolated fix; 2\u20133 for typical features; 4\u20135 for high-stakes subsystem touches; 6+ is a smell). lens framing follows Review mode: themed lenses (correctness & invariants, impact when new commits remove/rename/deprecate things, research-validated assumptions, security, user-journey, operational readiness, integration & cross-cutting, test integrity, performance, holistic) and subsystem lenses (auth, billing, schema migration, etc.) \u2014 for high-stakes domains lead with the subsystem lens rather than the generic themed equivalent.
|
|
702
|
+
otherwise pick lenses by where the new commits concentrate risk \u2014 **there's no fixed count**, same calibration as Review mode (1 lens for pure refactor / isolated fix; 2\u20133 for typical features; 4\u20135 for high-stakes subsystem touches; 6+ is a smell). same **lens-add discipline** as Review mode applies: each lens needs to name the concrete failure mode it would catch *that the new commits plausibly introduce* \u2014 "could apply" doesn't qualify, drop it. **research-validated assumptions** specifically: only pick when the new commits' correctness depends on a third-party contract behaving a specific way; merely using an API doesn't qualify. lens framing follows Review mode: themed lenses (correctness & invariants, impact when new commits remove/rename/deprecate things, research-validated assumptions, security, user-journey, operational readiness, integration & cross-cutting, test integrity, performance, holistic) and subsystem lenses (auth, billing, schema migration, etc.) \u2014 for high-stakes domains lead with the subsystem lens rather than the generic themed equivalent.
|
|
701
703
|
|
|
702
704
|
dispatch one \`${REVIEWER_AGENT_NAME}\` subagent per lens \u2014 its baked-in system prompt enforces the non-mutative + non-recursive contract (read-only file/search/web tools and read-only MCP queries; no writes, shell side effects, state-changing MCP calls, or nested subagent dispatch). dispatch them in a **single assistant turn with multiple parallel subagent calls** (serial dispatch collapses the fan-out). if a subagent errors out, times out, or returns nothing usable, retry once with the same lens; if it still fails, proceed with partial coverage and note the missing lens in the review body \u2014 do not skip step 5 entirely on a single subagent failure. each subagent gets:
|
|
703
705
|
- the diff scope (incremental diff path if available, full diff otherwise). do NOT tell them to skip pre-existing issues \u2014 that suppresses regressions the new commits amplified; the "issues must be NEW" filter lives at aggregation time (step 6), not in the subagent prompt
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trim surrounding whitespace from a sensitive value and register it as a
|
|
3
|
+
* GitHub Actions log mask. Trailing newlines from terminal-copy paste are a
|
|
4
|
+
* common footgun: the value travels through GH Actions logs and any tool
|
|
5
|
+
* that re-emits parts of it leaks the unmasked tail. Trimming canonicalises
|
|
6
|
+
* the value so the mask matches exactly what downstream tools will print.
|
|
7
|
+
*
|
|
8
|
+
* Masking is delegated to `core.setSecret` (not raw `console.log`) so the
|
|
9
|
+
* toolkit percent-encodes `\r`/`\n`; the runner V2 parser decodes them and
|
|
10
|
+
* registers the full value plus every non-empty line as separate masks. That
|
|
11
|
+
* keeps us safe for embedded-newline values (PEMs, kubeconfigs, JSON blobs)
|
|
12
|
+
* even though they aren't currently used.
|
|
13
|
+
*
|
|
14
|
+
* Returns the trimmed value, or `null` when the input was whitespace-only —
|
|
15
|
+
* callers must leave `process.env` untouched in that case so a misconfigured
|
|
16
|
+
* value surfaces as a clear "missing key" downstream rather than silently
|
|
17
|
+
* mutating to the empty string.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sanitizeSecret(key: string, value: string): string | null;
|
|
1
20
|
/**
|
|
2
21
|
* Normalize environment variables to uppercase.
|
|
3
22
|
* This handles case-insensitive env var names (e.g., `anthropic_api_key` -> `ANTHROPIC_API_KEY`).
|
|
@@ -5,6 +24,7 @@
|
|
|
5
24
|
* If there are conflicts (same key with different capitalizations but different values),
|
|
6
25
|
* logs a warning and keeps the uppercase version.
|
|
7
26
|
*
|
|
8
|
-
* Also
|
|
27
|
+
* Also trims and masks sensitive values so accidental trailing whitespace
|
|
28
|
+
* doesn't defeat GitHub Actions log masking.
|
|
9
29
|
*/
|
|
10
30
|
export declare function normalizeEnv(): void;
|
|
@@ -14,6 +14,27 @@ export declare function trackChild(options: TrackChildOptions): void;
|
|
|
14
14
|
export declare function untrackChild(child: ChildProcess): void;
|
|
15
15
|
export declare function setSignalHandler(handler: SignalHandler | null): void;
|
|
16
16
|
export declare function killTrackedChildren(): void;
|
|
17
|
+
/**
|
|
18
|
+
* Controls what the wrapper retains in memory across the child's lifetime
|
|
19
|
+
* for the post-hoc `SpawnResult.stdout` / `SpawnResult.stderr` snapshots.
|
|
20
|
+
*
|
|
21
|
+
* Streaming callbacks (`onStdout` / `onStderr`) fire regardless — `retain`
|
|
22
|
+
* only governs the buffered snapshot returned in `SpawnResult`.
|
|
23
|
+
*
|
|
24
|
+
* - `"tail"` (default): keep the last `maxRetainedBytes` UTF-16 code units
|
|
25
|
+
* of each stream. Once the cap is exceeded, oldest bytes are sliced off
|
|
26
|
+
* and the result is prefixed with a `... [N MiB truncated] ...` sentinel.
|
|
27
|
+
* Right default for short-lived commands whose failure mode is in their
|
|
28
|
+
* final output (git errors, install failures, hook scripts).
|
|
29
|
+
* - `"none"`: skip the buffer entirely. `SpawnResult.stdout` / `.stderr`
|
|
30
|
+
* are empty strings. Use this for long-lived streaming agents that already
|
|
31
|
+
* drain via `onStdout` / `onStderr` and never read the buffered snapshot.
|
|
32
|
+
*
|
|
33
|
+
* Default cap is 8 MiB — well below V8's ~1 GiB `kMaxLength` so `+= chunk`
|
|
34
|
+
* can never throw `RangeError: Invalid string length`.
|
|
35
|
+
*/
|
|
36
|
+
export type RetainMode = "tail" | "none";
|
|
37
|
+
export declare const DEFAULT_MAX_RETAINED_BYTES: number;
|
|
17
38
|
export interface SpawnOptions {
|
|
18
39
|
cmd: string;
|
|
19
40
|
args: string[];
|
|
@@ -27,6 +48,25 @@ export interface SpawnOptions {
|
|
|
27
48
|
onStdout?: (chunk: string) => void;
|
|
28
49
|
onStderr?: (chunk: string) => void;
|
|
29
50
|
killGroup?: boolean;
|
|
51
|
+
retain?: RetainMode;
|
|
52
|
+
maxRetainedBytes?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Bounded string accumulator that keeps the tail of appended chunks.
|
|
56
|
+
* Once the cap is exceeded, oldest bytes are sliced off and `toString()`
|
|
57
|
+
* prefixes the survivors with a sentinel describing the elided byte count.
|
|
58
|
+
*
|
|
59
|
+
* Exported because long-lived agent runtimes (opencode, claude) also
|
|
60
|
+
* accumulate per-run narration strings independently of the spawn wrapper
|
|
61
|
+
* and need the same protection against V8's `kMaxLength`.
|
|
62
|
+
*/
|
|
63
|
+
export declare class TailBuffer {
|
|
64
|
+
private readonly cap;
|
|
65
|
+
private buffer;
|
|
66
|
+
private truncatedBytes;
|
|
67
|
+
constructor(cap: number);
|
|
68
|
+
append(chunk: string): void;
|
|
69
|
+
toString(): string;
|
|
30
70
|
}
|
|
31
71
|
export interface SpawnResult {
|
|
32
72
|
stdout: string;
|
package/dist/utils/timer.d.ts
CHANGED
|
@@ -4,9 +4,20 @@ export declare class Timer {
|
|
|
4
4
|
constructor();
|
|
5
5
|
checkpoint(name: string): void;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Measures wall-clock gap between the last tool_result and the next tool_call,
|
|
9
|
+
* surfacing it as a "thought for Xs" log when over `THINKING_THRESHOLD`.
|
|
10
|
+
*
|
|
11
|
+
* Use one instance per logical session (orchestrator, each subagent) — sharing
|
|
12
|
+
* a single timer across sessions conflates cross-session interleaving as
|
|
13
|
+
* thinking time. The optional `formatLine` lets the caller prefix output with
|
|
14
|
+
* a session label so attribution is visible in the merged log stream.
|
|
15
|
+
*/
|
|
7
16
|
export declare class ThinkingTimer {
|
|
8
17
|
private readonly durationFormatter;
|
|
9
18
|
private lastToolResultTimestamp;
|
|
19
|
+
private readonly formatLine;
|
|
20
|
+
constructor(formatLine?: (line: string) => string);
|
|
10
21
|
markToolResult(): void;
|
|
11
22
|
markToolCall(): void;
|
|
12
23
|
}
|