pqcheck 0.16.15 → 0.16.16
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/README.md +1 -1
- package/bin/pqcheck.js +91 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/pqcheck)
|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
|
-
> **Latest: v0.16.
|
|
11
|
+
> **Latest: v0.16.16** — `pqcheck setup` now defaults to per-project install of the Claude Code statusLine + hooks (`./.claude/settings.json` instead of `~/.claude/settings.json`). The Cipherwake badge will only fire in projects where you ran setup, not across every Claude Code session on the machine. Pass `--scope global` to opt back into machine-wide install. [Full changelog →](./CHANGELOG.md)
|
|
12
12
|
|
|
13
13
|
## Two ways to use it
|
|
14
14
|
|
package/bin/pqcheck.js
CHANGED
|
@@ -4441,7 +4441,7 @@ function renderReleaseChecklist(domain, opts = {}) {
|
|
|
4441
4441
|
// `pqcheck init` — interactive workflow scaffold (habit-loop #4, locked 2026-05-16)
|
|
4442
4442
|
// =============================================================================
|
|
4443
4443
|
// Writes a ready-to-commit .github/workflows/cipherwake.yml that calls
|
|
4444
|
-
// cipherwakelabs/pqcheck
|
|
4444
|
+
// cipherwakelabs/pqcheck@v4 in trust-diff mode. Zero copy-paste docs friction.
|
|
4445
4445
|
//
|
|
4446
4446
|
// Flags:
|
|
4447
4447
|
// --domain <d> Skip the domain prompt
|
|
@@ -4618,7 +4618,7 @@ jobs:
|
|
|
4618
4618
|
runs-on: ubuntu-latest
|
|
4619
4619
|
steps:
|
|
4620
4620
|
- name: Run Cipherwake Trust Diff
|
|
4621
|
-
uses: cipherwakelabs/pqcheck
|
|
4621
|
+
uses: cipherwakelabs/pqcheck@v4
|
|
4622
4622
|
with:
|
|
4623
4623
|
mode: trust-diff
|
|
4624
4624
|
domain: ${domain}
|
|
@@ -5613,9 +5613,23 @@ async function runProtocolCommand(args) {
|
|
|
5613
5613
|
console.log("Here's what would be added:");
|
|
5614
5614
|
console.log("");
|
|
5615
5615
|
if (detected.length === 0) {
|
|
5616
|
+
// Default to creating project-local ./CLAUDE.md rather than global
|
|
5617
|
+
// ~/.claude/CLAUDE.md. Mirrors the cli/setup --scope=project default
|
|
5618
|
+
// (commit 4450e77): don't pollute global config when no signal exists
|
|
5619
|
+
// that the user wants machine-wide installation. Pass --scope=global
|
|
5620
|
+
// to override and create ~/.claude/CLAUDE.md instead.
|
|
5621
|
+
const scopeRaw = (parseFlag(args, "--scope") || "project").toLowerCase();
|
|
5622
|
+
const useGlobal = scopeRaw === "global";
|
|
5623
|
+
const fallbackPath = useGlobal
|
|
5624
|
+
? path.join(os.homedir(), ".claude", "CLAUDE.md")
|
|
5625
|
+
: path.join(process.cwd(), "CLAUDE.md");
|
|
5626
|
+
const fallbackDisplay = fallbackPath.replace(os.homedir(), "~");
|
|
5616
5627
|
console.log(color("dim", " No existing CLAUDE.md / .cursorrules / .aider.conf.yml found."));
|
|
5617
|
-
console.log(color("dim",
|
|
5618
|
-
|
|
5628
|
+
console.log(color("dim", ` Creating ${fallbackDisplay} with the protocol.`));
|
|
5629
|
+
if (!useGlobal) {
|
|
5630
|
+
console.log(color("dim", " (pass --scope global to create ~/.claude/CLAUDE.md instead)"));
|
|
5631
|
+
}
|
|
5632
|
+
detected.push({ label: useGlobal ? "Claude Code (will create global)" : "Claude Code (will create project)", path: fallbackPath });
|
|
5619
5633
|
}
|
|
5620
5634
|
for (const d of detected) {
|
|
5621
5635
|
console.log(` • Append a ~30-line "## Pre-deploy verification with Cipherwake" section to ${color("bold", d.path)}`);
|
|
@@ -5927,11 +5941,21 @@ async function runSetupCommand(args) {
|
|
|
5927
5941
|
const skipHook = args.includes("--skip-hook");
|
|
5928
5942
|
const skipStatusline = args.includes("--skip-statusline");
|
|
5929
5943
|
const skipVscode = args.includes("--skip-vscode");
|
|
5944
|
+
// Where to install Claude Code statusLine + hooks. Default 'project' keeps
|
|
5945
|
+
// Cipherwake scoped to the current repo (avoids the "Cipherwake badge
|
|
5946
|
+
// follows me into unrelated projects" UX bug). 'global' is opt-in for
|
|
5947
|
+
// power users who want one canonical domain badge across every project.
|
|
5948
|
+
const settingsScope = (parseFlag(args, "--scope") || "project").toLowerCase();
|
|
5949
|
+
if (!["project", "global"].includes(settingsScope)) {
|
|
5950
|
+
console.error(color("red", `error: --scope must be 'project' or 'global' (got '${settingsScope}')`));
|
|
5951
|
+
process.exit(3);
|
|
5952
|
+
}
|
|
5930
5953
|
|
|
5931
5954
|
if (!domain) {
|
|
5932
5955
|
console.error(color("red", "error: pqcheck setup requires --domain"));
|
|
5933
5956
|
console.error(color("dim", "Usage: npx pqcheck setup --auto --domain example.com"));
|
|
5934
|
-
console.error(color("dim", " --plan
|
|
5957
|
+
console.error(color("dim", " --plan Print the install plan without writing any files"));
|
|
5958
|
+
console.error(color("dim", " --scope project|global Where to install Claude Code hooks (default: project — this repo only)"));
|
|
5935
5959
|
console.error(color("dim", " --invoked-by=\"<name>\" --consent-phrase=\"<words>\" (audit trail)"));
|
|
5936
5960
|
console.error(color("dim", "Skip flags: --skip-workflow --skip-protocol --skip-hook --skip-statusline --skip-vscode"));
|
|
5937
5961
|
process.exit(3);
|
|
@@ -5977,11 +6001,17 @@ async function runSetupCommand(args) {
|
|
|
5977
6001
|
}
|
|
5978
6002
|
if (!skipHook) planEntries.push({ what: "git pre-push hook", to: path.join(process.cwd(), ".git", "hooks", "pre-push"), op: "create (if git repo)" });
|
|
5979
6003
|
if (!skipStatusline) {
|
|
5980
|
-
const settingsPath =
|
|
6004
|
+
const settingsPath = settingsScope === "global"
|
|
6005
|
+
? path.join(os.homedir(), ".claude", "settings.json")
|
|
6006
|
+
: path.join(process.cwd(), ".claude", "settings.json");
|
|
5981
6007
|
let exists = false;
|
|
5982
6008
|
try { await fs.access(settingsPath); exists = true; } catch { /* */ }
|
|
5983
|
-
|
|
6009
|
+
const scopeNote = settingsScope === "global"
|
|
6010
|
+
? " [GLOBAL — fires in every project on this machine]"
|
|
6011
|
+
: " [PROJECT-LOCAL — fires only when Claude Code runs in this directory]";
|
|
6012
|
+
planEntries.push({ what: `Claude Code statusLine${scopeNote}`, to: settingsPath, op: exists ? "deep-merge (backup first)" : "create" });
|
|
5984
6013
|
planEntries.push({ what: "Claude Code chat-hook (PostToolUse Bash)", to: settingsPath, op: exists ? "deep-merge into hooks.PostToolUse" : "create" });
|
|
6014
|
+
planEntries.push({ what: "Claude Code prompt-hook (UserPromptSubmit)", to: settingsPath, op: exists ? "deep-merge into hooks.UserPromptSubmit" : "create" });
|
|
5985
6015
|
}
|
|
5986
6016
|
if (!skipVscode) planEntries.push({ what: "VS Code / Cursor extension", to: "via `code --install-extension cipherwakelabs.cipherwake-statusbar`", op: "attempt (soft-fail if Marketplace listing missing)" });
|
|
5987
6017
|
for (const e of planEntries) {
|
|
@@ -6012,6 +6042,48 @@ async function runSetupCommand(args) {
|
|
|
6012
6042
|
console.log("");
|
|
6013
6043
|
}
|
|
6014
6044
|
|
|
6045
|
+
// Resolve where to write Claude Code statusLine + hook entries.
|
|
6046
|
+
// Default 'project' = ./.claude/settings.json (this repo only). 'global' = ~/.claude/settings.json
|
|
6047
|
+
// (fires in every Claude Code session on this machine). Project-local is the right default for
|
|
6048
|
+
// anyone with multiple projects; global is the legacy behavior and now an opt-in for the
|
|
6049
|
+
// "one canonical domain across everything" use case.
|
|
6050
|
+
const claudeSettingsPath = settingsScope === "global"
|
|
6051
|
+
? path.join(os.homedir(), ".claude", "settings.json")
|
|
6052
|
+
: path.join(process.cwd(), ".claude", "settings.json");
|
|
6053
|
+
|
|
6054
|
+
if (!skipStatusline) {
|
|
6055
|
+
const displayPath = claudeSettingsPath.replace(os.homedir(), "~");
|
|
6056
|
+
const scopeLabel = settingsScope === "global"
|
|
6057
|
+
? `global (${displayPath} — fires in EVERY project on this machine)`
|
|
6058
|
+
: `project (${displayPath} — fires only when Claude Code runs in this directory)`;
|
|
6059
|
+
console.log(color("dim", `Settings scope: ${scopeLabel}`));
|
|
6060
|
+
console.log(color("dim", settingsScope === "global"
|
|
6061
|
+
? ` (omit --scope or pass --scope project to install for this repo only)`
|
|
6062
|
+
: ` (pass --scope global to install for every project on this machine)`));
|
|
6063
|
+
console.log("");
|
|
6064
|
+
|
|
6065
|
+
// Detect existing GLOBAL Cipherwake install while doing a project install.
|
|
6066
|
+
// Warn so the user doesn't end up with two layers firing on top of each other.
|
|
6067
|
+
if (settingsScope === "project") {
|
|
6068
|
+
try {
|
|
6069
|
+
const globalPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
6070
|
+
const raw = await fs.readFile(globalPath, "utf8");
|
|
6071
|
+
const parsed = JSON.parse(raw);
|
|
6072
|
+
const hasGlobalCipherwake =
|
|
6073
|
+
(parsed.statusLine?.command && String(parsed.statusLine.command).includes("cipherwake")) ||
|
|
6074
|
+
JSON.stringify(parsed.hooks || {}).includes("cipherwake");
|
|
6075
|
+
if (hasGlobalCipherwake) {
|
|
6076
|
+
console.log(color("yellow", ` ⚠ A GLOBAL Cipherwake install already exists at ~/.claude/settings.json`));
|
|
6077
|
+
console.log(color("dim", ` It will continue firing across every project. To remove the global install,`));
|
|
6078
|
+
console.log(color("dim", ` edit ~/.claude/settings.json and delete the statusLine entry and the`));
|
|
6079
|
+
console.log(color("dim", ` cipherwake-chat-hook / cipherwake-prompt-hook commands. Backups are taken`));
|
|
6080
|
+
console.log(color("dim", ` automatically before any write, so changes are reversible.`));
|
|
6081
|
+
console.log("");
|
|
6082
|
+
}
|
|
6083
|
+
} catch { /* no global install — fine */ }
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
6086
|
+
|
|
6015
6087
|
const installSummary = [];
|
|
6016
6088
|
|
|
6017
6089
|
// -------------------------------------------------------------------------
|
|
@@ -6176,7 +6248,8 @@ async function runSetupCommand(args) {
|
|
|
6176
6248
|
}
|
|
6177
6249
|
|
|
6178
6250
|
if (!skipStatusline) {
|
|
6179
|
-
const settingsPath =
|
|
6251
|
+
const settingsPath = claudeSettingsPath;
|
|
6252
|
+
const displayPath = settingsPath.replace(os.homedir(), "~");
|
|
6180
6253
|
try {
|
|
6181
6254
|
let settings = {};
|
|
6182
6255
|
let existed = false;
|
|
@@ -6187,7 +6260,7 @@ async function runSetupCommand(args) {
|
|
|
6187
6260
|
} catch { /* will create */ }
|
|
6188
6261
|
if (existed && settings.statusLine && typeof settings.statusLine === "object") {
|
|
6189
6262
|
// Already has a statusLine config — don't overwrite.
|
|
6190
|
-
console.log(color("dim", ` ⊝
|
|
6263
|
+
console.log(color("dim", ` ⊝ ${displayPath} already has a statusLine entry — leaving alone`));
|
|
6191
6264
|
console.log(color("dim", ` To use the Cipherwake statusline instead, set: "command": "npx --package=pqcheck@latest cipherwake-statusline"`));
|
|
6192
6265
|
installSummary.push({ component: "Claude Code statusLine", path: settingsPath, status: "skipped-existing-config" });
|
|
6193
6266
|
} else {
|
|
@@ -6196,7 +6269,7 @@ async function runSetupCommand(args) {
|
|
|
6196
6269
|
settings.statusLine = { type: "command", command: "npx --package=pqcheck@latest cipherwake-statusline" };
|
|
6197
6270
|
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
6198
6271
|
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
6199
|
-
console.log(color("green", ` ✓ added statusLine config →
|
|
6272
|
+
console.log(color("green", ` ✓ added statusLine config → ${displayPath}`));
|
|
6200
6273
|
installSummary.push({ component: "Claude Code statusLine", path: settingsPath, status: existed ? "installed-updated" : "installed-created", backup: backupPath });
|
|
6201
6274
|
}
|
|
6202
6275
|
} catch (err) {
|
|
@@ -6212,7 +6285,8 @@ async function runSetupCommand(args) {
|
|
|
6212
6285
|
// doesn't clobber other hooks per CLAUDE.md Rule 17.
|
|
6213
6286
|
// -------------------------------------------------------------------------
|
|
6214
6287
|
if (!skipStatusline) {
|
|
6215
|
-
const settingsPath =
|
|
6288
|
+
const settingsPath = claudeSettingsPath;
|
|
6289
|
+
const displayPath = settingsPath.replace(os.homedir(), "~");
|
|
6216
6290
|
try {
|
|
6217
6291
|
let settings = {};
|
|
6218
6292
|
let existed = false;
|
|
@@ -6238,7 +6312,7 @@ async function runSetupCommand(args) {
|
|
|
6238
6312
|
);
|
|
6239
6313
|
|
|
6240
6314
|
if (alreadyInstalled) {
|
|
6241
|
-
console.log(color("dim", ` ⊝ chat-hook already configured in
|
|
6315
|
+
console.log(color("dim", ` ⊝ chat-hook already configured in ${displayPath} PostToolUse — skipping`));
|
|
6242
6316
|
installSummary.push({ component: "Claude Code chat-hook", path: settingsPath, status: "skipped-already-present" });
|
|
6243
6317
|
} else {
|
|
6244
6318
|
const backupPath = existed ? await backupSettingsJson(settingsPath) : null;
|
|
@@ -6246,7 +6320,7 @@ async function runSetupCommand(args) {
|
|
|
6246
6320
|
bashEntry.hooks.push({ type: "command", command: cipherwakeHookCmd });
|
|
6247
6321
|
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
6248
6322
|
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
6249
|
-
console.log(color("green", ` ✓ added chat-hook (PostToolUse Bash) →
|
|
6323
|
+
console.log(color("green", ` ✓ added chat-hook (PostToolUse Bash) → ${displayPath}`));
|
|
6250
6324
|
console.log(color("dim", ` Every \`pqcheck\` run will now push a live status message into Claude Code chat`));
|
|
6251
6325
|
installSummary.push({ component: "Claude Code chat-hook", path: settingsPath, status: existed ? "installed-updated" : "installed-created", backup: backupPath });
|
|
6252
6326
|
}
|
|
@@ -6265,7 +6339,8 @@ async function runSetupCommand(args) {
|
|
|
6265
6339
|
// / ship_decision=pass (no spam).
|
|
6266
6340
|
// -------------------------------------------------------------------------
|
|
6267
6341
|
if (!skipStatusline) {
|
|
6268
|
-
const settingsPath =
|
|
6342
|
+
const settingsPath = claudeSettingsPath;
|
|
6343
|
+
const displayPath = settingsPath.replace(os.homedir(), "~");
|
|
6269
6344
|
try {
|
|
6270
6345
|
let settings = {};
|
|
6271
6346
|
let existed = false;
|
|
@@ -6285,7 +6360,7 @@ async function runSetupCommand(args) {
|
|
|
6285
6360
|
);
|
|
6286
6361
|
|
|
6287
6362
|
if (alreadyInstalled) {
|
|
6288
|
-
console.log(color("dim", ` ⊝ prompt-hook already configured in
|
|
6363
|
+
console.log(color("dim", ` ⊝ prompt-hook already configured in ${displayPath} UserPromptSubmit — skipping`));
|
|
6289
6364
|
installSummary.push({ component: "Claude Code prompt-hook", path: settingsPath, status: "skipped-already-present" });
|
|
6290
6365
|
} else {
|
|
6291
6366
|
const backupPath = existed ? await backupSettingsJson(settingsPath) : null;
|
|
@@ -6293,7 +6368,7 @@ async function runSetupCommand(args) {
|
|
|
6293
6368
|
settings.hooks.UserPromptSubmit.push({ hooks: [{ type: "command", command: cipherwakeHookCmd }] });
|
|
6294
6369
|
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
6295
6370
|
await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
6296
|
-
console.log(color("green", ` ✓ added prompt-hook (UserPromptSubmit) →
|
|
6371
|
+
console.log(color("green", ` ✓ added prompt-hook (UserPromptSubmit) → ${displayPath}`));
|
|
6297
6372
|
console.log(color("dim", ` Claude will see latest ship_decision in context on every prompt (when REVIEW/BLOCK)`));
|
|
6298
6373
|
installSummary.push({ component: "Claude Code prompt-hook", path: settingsPath, status: existed ? "installed-updated" : "installed-created", backup: backupPath });
|
|
6299
6374
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pqcheck",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.16",
|
|
4
4
|
"description": "Deploy gate for AI-coded web apps. `pqcheck deploy-check --ai` returns ship_decision=pass|review|block for Claude Code / Cursor / Copilot / Aider to gate deploys before they ship. Anonymous, no signup, free for first use.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-coder",
|