diff --git a/package-lock.json b/package-lock.json index fe95ab5..cf2cf54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17312,8 +17312,8 @@ "license": "MIT", "dependencies": { "@shikijs/cli": "^4.0.2", - "@sting8k/pi-vcc": "^0.3.5", - "@tintinweb/pi-tasks": "^0.4.2", + "@sting8k/pi-vcc": "0.3.18", + "@tintinweb/pi-tasks": "0.4.3", "better-sqlite3": "^12.10.0", "diff": "^7.0.0", "semver": "^7.8.5" diff --git a/packages/pi-ext/THIRD_PARTY_NOTICES.md b/packages/pi-ext/THIRD_PARTY_NOTICES.md index 6b88a9f..65560ea 100644 --- a/packages/pi-ext/THIRD_PARTY_NOTICES.md +++ b/packages/pi-ext/THIRD_PARTY_NOTICES.md @@ -19,6 +19,12 @@ its original license remains at `extensions/tool-presentation/tidy/LICENSE`. Expanded edit/write rendering is derived from [pi-diff](https://github.com/buddingnewinsights/pi-diff) by @heyhuynhgiabuu. +## handoff + +The handoff redaction helper is adapted from +[sting8k/pi-vcc](https://github.com/sting8k/pi-vcc) 0.3.5, licensed under MIT +according to its published package metadata. + ## ask-user-question Derived from [ghoseb/pi-askuserquestion](https://github.com/ghoseb/pi-askuserquestion), licensed under MIT. diff --git a/packages/pi-ext/extensions/handoff/index.ts b/packages/pi-ext/extensions/handoff/index.ts index a66fbf2..4c24845 100644 --- a/packages/pi-ext/extensions/handoff/index.ts +++ b/packages/pi-ext/extensions/handoff/index.ts @@ -38,8 +38,8 @@ import { import { normalize } from "@sting8k/pi-vcc/src/core/normalize"; import { filterNoise } from "@sting8k/pi-vcc/src/core/filter-noise"; import { buildSections } from "@sting8k/pi-vcc/src/core/build-sections"; -import { redact } from "@sting8k/pi-vcc/src/core/redact"; import { TaskStore } from "@tintinweb/pi-tasks/src/task-store.js"; +import { redact } from "./redact.js"; function shellQuote(value: string): string { if (value.length === 0) return "''"; diff --git a/packages/pi-ext/extensions/handoff/redact.ts b/packages/pi-ext/extensions/handoff/redact.ts new file mode 100644 index 0000000..0055ddd --- /dev/null +++ b/packages/pi-ext/extensions/handoff/redact.ts @@ -0,0 +1,10 @@ +// Adapted from @sting8k/pi-vcc 0.3.5 under MIT; see THIRD_PARTY_NOTICES.md. +const SENSITIVE_RE = + /(?:sshpass\s+-p\s*'[^']*'|sshpass\s+-p\s*"[^"]*"|sshpass\s+-p\s*\S+|password[=:]\s*\S+|api[_-]?key[=:]\s*\S+|secret[=:]\s*\S+|token[=:]\s*[A-Za-z0-9_\-.]{8,}|-i\s+\S+\.pem\b)/gi; + +export function redact(text: string): string { + return text.replace(SENSITIVE_RE, (match) => { + const prefix = match.split(/[=:\s]+/)[0]; + return `${prefix} [REDACTED]`; + }); +} diff --git a/packages/pi-ext/package.json b/packages/pi-ext/package.json index 2962acc..159c381 100644 --- a/packages/pi-ext/package.json +++ b/packages/pi-ext/package.json @@ -22,8 +22,8 @@ ], "dependencies": { "@shikijs/cli": "^4.0.2", - "@sting8k/pi-vcc": "^0.3.5", - "@tintinweb/pi-tasks": "^0.4.2", + "@sting8k/pi-vcc": "0.3.18", + "@tintinweb/pi-tasks": "0.4.3", "better-sqlite3": "^12.10.0", "diff": "^7.0.0", "semver": "^7.8.5" @@ -61,7 +61,7 @@ "homepage": "https://github.com/iurysza/pi-extensions/tree/main/packages/pi-ext#readme", "scripts": { "sem:evaluate": "node extensions/pi-sem/bin/sem-eval.mjs", - "test": "npm run test:pi-sem && npm run test:tool-presentation && node --test tests/leader-key/favourite-models.test.mjs tests/chat-to-md/chat-to-md.test.mjs", + "test": "npm run test:pi-sem && npm run test:tool-presentation && node --test tests/leader-key/favourite-models.test.mjs tests/chat-to-md/chat-to-md.test.mjs tests/handoff/dependency-contract.test.mjs", "test:pi-sem": "node --test tests/pi-sem/core.test.mjs tests/review/sem-guidance.test.mjs", "test:tool-presentation": "node scripts/test-tool-presentation.mjs", "typecheck": "tsc --noEmit -p tsconfig.json", diff --git a/packages/pi-ext/tests/handoff/dependency-contract.test.mjs b/packages/pi-ext/tests/handoff/dependency-contract.test.mjs new file mode 100644 index 0000000..a6b9698 --- /dev/null +++ b/packages/pi-ext/tests/handoff/dependency-contract.test.mjs @@ -0,0 +1,39 @@ +import assert from "node:assert/strict"; +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { describe, it } from "node:test"; + +import { discoverAndLoadExtensions } from "@earendil-works/pi-coding-agent"; +import { redact } from "../../extensions/handoff/redact.ts"; + +const packageRoot = resolve(import.meta.dirname, "../.."); +const handoffEntry = resolve(packageRoot, "extensions/handoff/index.ts"); + +describe("handoff dependency contract", () => { + it("pins and loads the internal modules shipped by its dependencies", async () => { + const manifest = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8")); + assert.equal(manifest.dependencies["@sting8k/pi-vcc"], "0.3.18"); + assert.equal(manifest.dependencies["@tintinweb/pi-tasks"], "0.4.3"); + + const agentDir = await mkdtemp(join(tmpdir(), "pi-ext-handoff-loader-")); + try { + const loaded = await discoverAndLoadExtensions([handoffEntry], packageRoot, agentDir); + assert.deepEqual(loaded.errors, []); + assert.equal(loaded.extensions.length, 1); + assert.equal(loaded.extensions[0]?.resolvedPath, handoffEntry); + assert.ok(loaded.extensions[0]?.commands.has("handoff")); + } finally { + await rm(agentDir, { recursive: true, force: true }); + } + }); + + it("retains the removed upstream secret redaction behavior locally", () => { + const input = "password=hunter2 api_key: abc123 secret=hidden token=abcdefgh -i deploy.pem"; + assert.equal( + redact(input), + "password [REDACTED] api_key [REDACTED] secret [REDACTED] token [REDACTED] -i [REDACTED]", + ); + assert.equal(redact("ordinary handoff text"), "ordinary handoff text"); + }); +}); diff --git a/packages/pi-ext/types/pi-vcc-core.d.ts b/packages/pi-ext/types/pi-vcc-core.d.ts index 38e4e00..c54583e 100644 --- a/packages/pi-ext/types/pi-vcc-core.d.ts +++ b/packages/pi-ext/types/pi-vcc-core.d.ts @@ -10,4 +10,3 @@ export function normalize(messages: readonly unknown[]): unknown[]; export function filterNoise(blocks: unknown[]): unknown[]; export function buildSections(input: { blocks: unknown[] }): VccSections; export function sanitize(text: string): string; -export function redact(text: string): string;