Skip to content

Commit b92e2e3

Browse files
committed
clean up
1 parent 712cf36 commit b92e2e3

13 files changed

Lines changed: 148 additions & 32 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ cache
88
.DS_Store
99
p10k-instant-prompt-*.zsh
1010
**/node_modules
11+
**/__pycache__/

bin/redact-secrets

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import os
5+
import subprocess
6+
import sys
7+
import tempfile
8+
from pathlib import Path
9+
10+
SUCCESS_CODES = {0, 200, 205}
11+
12+
13+
def scan(path: Path) -> list[dict[str, int]]:
14+
result = subprocess.run(
15+
[
16+
"kingfisher",
17+
"scan",
18+
str(path),
19+
"--git-history",
20+
"none",
21+
"--only-valid",
22+
"--redact",
23+
"--no-dedup",
24+
"--format",
25+
"jsonl",
26+
"--no-update-check",
27+
],
28+
capture_output=True,
29+
text=True,
30+
check=False,
31+
)
32+
if result.returncode not in SUCCESS_CODES:
33+
raise RuntimeError(
34+
f"Kingfisher failed for {path} with exit code {result.returncode}"
35+
)
36+
37+
return [
38+
record["finding"]
39+
for line in result.stdout.splitlines()
40+
if line and (record := json.loads(line)).get("finding")
41+
]
42+
43+
44+
def redact(path: Path, findings: list[dict[str, int]]) -> None:
45+
lines = path.read_text().splitlines(keepends=True)
46+
by_line: dict[int, list[dict[str, int]]] = {}
47+
for finding in findings:
48+
by_line.setdefault(finding["line"], []).append(finding)
49+
50+
for line_number, matches in by_line.items():
51+
line = list(lines[line_number - 1])
52+
for finding in sorted(
53+
matches, key=lambda item: item["column_start"], reverse=True
54+
):
55+
start = finding["column_start"]
56+
end = finding["column_end"] + 1
57+
if start < 0 or end > len(line) or start >= end:
58+
raise RuntimeError(
59+
f"Invalid Kingfisher range in {path} at line {line_number}"
60+
)
61+
line[start:end] = "*" * (end - start)
62+
lines[line_number - 1] = "".join(line)
63+
64+
mode = path.stat().st_mode
65+
fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.redact-", dir=path.parent)
66+
try:
67+
with os.fdopen(fd, "w") as file:
68+
file.writelines(lines)
69+
os.chmod(temporary, mode)
70+
os.replace(temporary, path)
71+
except BaseException:
72+
Path(temporary).unlink(missing_ok=True)
73+
raise
74+
75+
76+
def main() -> int:
77+
paths = [
78+
Path(argument).expanduser().resolve()
79+
for argument in sys.argv[1:]
80+
if argument != "--"
81+
]
82+
if not paths:
83+
print("Usage: redact-secrets -- <file>...", file=sys.stderr)
84+
return 2
85+
86+
for path in paths:
87+
print(f"Scanning {path}", flush=True)
88+
findings = scan(path)
89+
if not findings:
90+
print(f"No validated secrets in {path}", flush=True)
91+
continue
92+
redact(path, findings)
93+
remaining = scan(path)
94+
if remaining:
95+
raise RuntimeError(f"Validated secrets remain in {path}")
96+
print(f"Redacted {len(findings)} occurrence(s) from {path}", flush=True)
97+
98+
return 0
99+
100+
101+
if __name__ == "__main__":
102+
try:
103+
raise SystemExit(main())
104+
except (OSError, RuntimeError, UnicodeError, json.JSONDecodeError) as error:
105+
print(f"redact-secrets: {error}", file=sys.stderr)
106+
raise SystemExit(1)

pi/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
## General Behavior
66

77
- Start with the simplest implementation that satisfies the stated requirements and existing tests. Before adding an abstraction, guard, or edge-case handling, name (to yourself) the concrete requirement, failing test, observed failure, or established repository convention it addresses -- with file:line if it's a type or call site. If you cannot, leave it out. When the answer is a lookup question (can this be null? who are the callers? does this already exist in the repo?), go look it up rather than hedging.
8+
- Treat Jira tickets as work-tracking context, not an exhaustive specification. Do not remove or label diff behavior speculative merely because it is absent from the ticket; use the diff, surrounding code, tests, and user direction to establish intent. Ask before removing behavior when intent remains unclear.
89
- In general, opt for existing tools (formatters, linters, etc) for fixing problems where possible instead of manual edits
910
- After implementation, reread the complete diff and remove speculative abstractions, checks, and indirection.
1011
- Avoid meta commentary when writing docs, comments, or PR descriptions. Don't make arguments against previous iterations that used to exist -- keep text artifacts grounded in the present.

pi/extensions/.oxlintrc.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@
99
"typescript/no-floating-promises": "off",
1010
"typescript/unbound-method": "off"
1111
}
12-
},
13-
{
14-
"files": ["output-compaction.ts"],
15-
"rules": {
16-
"typescript/no-base-to-string": "off",
17-
"typescript/unbound-method": "off"
18-
}
1912
}
2013
]
2114
}

pi/extensions/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- Respect `ctx.isProjectTrusted()` before loading project-controlled configuration or instructions.
1212
- Validate persisted, network, subprocess, and session data at module boundaries.
1313
- Do not use `any` outside a documented compatibility adapter.
14-
- Keep undocumented Pi internals and prototype patches inside `shared/*-compat.ts` modules with installed-runtime tests.
14+
- Keep undocumented Pi internals and prototype patches inside extension-owned `compat.ts` modules with installed-runtime tests.
1515
- Add a regression test for every bug fix.
1616
- Declare dependencies in the root manifest. Do not add nested manifests or lockfiles.
1717
- Provision external executables through `Brewfile`; extensions only detect and report missing dependencies.

pi/extensions/output-compaction.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* oxlint-disable typescript/no-base-to-string, typescript/unbound-method */
12
import { readFileSync, realpathSync } from "node:fs";
23
import { createRequire } from "node:module";
34
import { dirname, join, resolve } from "node:path";
@@ -188,7 +189,7 @@ function sameMembers(left: ToolRow[], right: ToolRow[]): boolean {
188189
return left.length === right.length && left.every((member, index) => member === right[index]);
189190
}
190191

191-
function renderCompactBlock(rows: ToolRow[], width: number, state: ToolPatchState): string[] {
192+
function renderCompactBlock(rows: [ToolRow, ...ToolRow[]], width: number, state: ToolPatchState): string[] {
192193
const theme = state.theme;
193194
if (!theme) return state.originalRender.call(rows[0], width);
194195

@@ -254,7 +255,7 @@ function renderContainer(container: ContainerLike, width: number, state: Contain
254255
continue;
255256
}
256257

257-
const group: ToolRow[] = [child];
258+
const group: [ToolRow, ...ToolRow[]] = [child];
258259
let lastMember = index;
259260
for (let candidateIndex = index + 1; candidateIndex < children.length; candidateIndex++) {
260261
const candidate = children[candidateIndex];

pi/extensions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"lint": "oxlint --type-aware --deny-warnings --report-unused-disable-directives .",
1111
"prepare": "effect-tsgo patch",
1212
"test": "node --test --experimental-strip-types",
13-
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.strict.json"
13+
"typecheck": "tsc --noEmit"
1414
},
1515
"dependencies": {
1616
"acorn": "8.17.0",

pi/extensions/session-secret-redaction/index.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ for (const scanExitCode of [200, 205]) {
1717
temporaryDirectories.push(directory);
1818
const sessionFile = join(directory, "session.jsonl");
1919
const secret = "0123456789abcdef0123456789abcdef";
20-
const content = `${JSON.stringify({ type: "message", message: { content: `{"api_key": "${secret}"}` } })}\n`;
20+
const content = `${JSON.stringify({
21+
type: "message",
22+
message: { content: `{"api_key": "${secret}", "duplicate": "${secret}"}` },
23+
})}\n`;
2124
await writeFile(sessionFile, content);
2225

23-
const start = content.indexOf(secret);
24-
const finding = {
26+
const starts = [content.indexOf(secret), content.lastIndexOf(secret)];
27+
const findings = starts.map((start) => ({
2528
finding: {
2629
line: 1,
2730
column_start: start,
2831
column_end: start + secret.length - 1,
2932
},
30-
};
33+
}));
3134
let sessionStart: ((event: unknown, ctx: unknown) => Promise<void>) | undefined;
3235
const commands: string[] = [];
3336
const pi = {
@@ -37,7 +40,12 @@ for (const scanExitCode of [200, 205]) {
3740
async exec(command: string, args: string[]) {
3841
commands.push([command, ...args].join(" "));
3942
if (args[0] === "--version") return { code: 0, stdout: "kingfisher", stderr: "" };
40-
return { code: scanExitCode, stdout: `${JSON.stringify(finding)}\n`, stderr: "" };
43+
const reportedFindings = args.includes("--no-dedup") ? findings : findings.slice(0, 1);
44+
return {
45+
code: scanExitCode,
46+
stdout: `${reportedFindings.map((finding) => JSON.stringify(finding)).join("\n")}\n`,
47+
stderr: "",
48+
};
4149
},
4250
};
4351
extension(pi as never);
@@ -55,12 +63,12 @@ for (const scanExitCode of [200, 205]) {
5563
const result = await readFile(sessionFile, "utf8");
5664
assert.equal(Buffer.byteLength(result), Buffer.byteLength(content));
5765
assert.equal(result.includes(secret), false);
58-
assert.equal(result.includes("*".repeat(secret.length)), true);
66+
assert.equal(result.split("*".repeat(secret.length)).length - 1, 2);
5967
assert.doesNotThrow(() => JSON.parse(result));
60-
assert.deepEqual(notifications, ["Redacted 1 validated secret from this session."]);
68+
assert.deepEqual(notifications, ["Redacted 2 validated secrets from this session."]);
6169
assert.deepEqual(commands.slice(0, 2), [
6270
"kingfisher --version",
63-
`kingfisher scan ${sessionFile} --git-history none --only-valid --redact --format jsonl --no-update-check`,
71+
`kingfisher scan ${sessionFile} --git-history none --only-valid --redact --no-dedup --format jsonl --no-update-check`,
6472
]);
6573
});
6674
}

pi/extensions/session-secret-redaction/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ async function redactSession(pi: ExtensionAPI, ctx: ExtensionContext): Promise<n
8181
"none",
8282
"--only-valid",
8383
"--redact",
84+
"--no-dedup",
8485
"--format",
8586
"jsonl",
8687
"--no-update-check",

pi/extensions/tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
"lib": ["ESNext"],
77
"types": ["node"],
88
"strict": true,
9+
"exactOptionalPropertyTypes": true,
10+
"noUncheckedIndexedAccess": true,
11+
"noUnusedLocals": true,
12+
"noUnusedParameters": true,
913
"noEmit": true,
1014
"skipLibCheck": true,
1115
"allowImportingTsExtensions": true,

0 commit comments

Comments
 (0)