diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 784a897..0b51919 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -7,14 +7,14 @@ }, "metadata": { "description": "Verified Solution Cards retrieved from an agent-authored conceptual problem statement.", - "version": "0.4.2" + "version": "0.4.3" }, "plugins": [ { "name": "blaze", "source": "./plugins/claude-code", "description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.", - "version": "0.4.2", + "version": "0.4.3", "keywords": [ "memory", "retrieval", diff --git a/install.md b/install.md index 284a579..a2f54be 100644 --- a/install.md +++ b/install.md @@ -1,4 +1,4 @@ -# Install Blaze 0.4.2 +# Install Blaze 0.4.3 Blaze shares verified coding lessons through an explicitly invoked client. Installing it does not authorize uploading prompts, source or transcripts. diff --git a/plugins/claude-code/.claude-plugin/plugin.json b/plugins/claude-code/.claude-plugin/plugin.json index ed7ecba..570e978 100644 --- a/plugins/claude-code/.claude-plugin/plugin.json +++ b/plugins/claude-code/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://anthropic.com/claude-code/plugin.schema.json", "name": "blaze", "displayName": "Blaze", - "version": "0.4.2", + "version": "0.4.3", "description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.", "author": { "name": "Blaze" diff --git a/plugins/claude-code/blaze-client.mjs b/plugins/claude-code/blaze-client.mjs index fcd1ea1..c76baeb 100644 --- a/plugins/claude-code/blaze-client.mjs +++ b/plugins/claude-code/blaze-client.mjs @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.4.2"; +export const CLIENT_VERSION = "0.4.3"; export const CLIENT_CONTRACT = 1; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"]; @@ -39,7 +39,6 @@ const VERIFICATIONS = new Set(["passed", "failed", "not_run", "unknown"]); const CONTRIBUTION_STATES = new Set(["queued", "evaluating", "accepted", "rejected", "failed", "revoked"]); const PARTICIPATION_STATUSES = new Set(["pending", "contributed", "no_novel_solution", "privacy_skip", "verification_missing", "not_solved", "not_applicable"]); const BOUNDARIES = new Set(["task_start_to_agent_end", "task_start_to_verification_end"]); -const ENDS = new Set(["stop", "subagentstop", "sessionend", "session.idle", "sessioncompleted"]); const positiveDuration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 7 * 24 * 60 * 60 * 1000; const wallNow = () => performance.timeOrigin + performance.now(); const DURATION_PATTERN = "(?:0s|<0\\.01s|[0-9]{1,9}\\.[0-9]{1,2}s)"; @@ -465,14 +464,14 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre return { deleted: true }; }, async hook(body) { - const event = String(body.hook_event_name ?? body.event ?? "UserPromptSubmit"); - if (ENDS.has(event.toLowerCase())) return {}; + const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; + if (event !== "UserPromptSubmit") return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, "Inspect the exact query before sending it. Local validation is a guardrail, not proof that text is safe to disclose.", ].join("\n"); - return { additionalContext, hookSpecificOutput: { hookEventName: event, additionalContext } }; + return { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext } }; }, async lookup(body) { return retrieve(body, "UserPromptSubmit"); }, async card(decisionId, cardId) { diff --git a/plugins/claude-code/skills/blaze/SKILL.md b/plugins/claude-code/skills/blaze/SKILL.md index 93eb205..e4d3d2c 100644 --- a/plugins/claude-code/skills/blaze/SKILL.md +++ b/plugins/claude-code/skills/blaze/SKILL.md @@ -3,7 +3,7 @@ name: blaze description: Reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition. compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local reminder hooks need no network access. No model provider credentials are needed. metadata: - version: "0.4.2" + version: "0.4.3" --- # Blaze diff --git a/plugins/claude-code/skills/blaze/blaze-client.mjs b/plugins/claude-code/skills/blaze/blaze-client.mjs index fcd1ea1..c76baeb 100644 --- a/plugins/claude-code/skills/blaze/blaze-client.mjs +++ b/plugins/claude-code/skills/blaze/blaze-client.mjs @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.4.2"; +export const CLIENT_VERSION = "0.4.3"; export const CLIENT_CONTRACT = 1; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"]; @@ -39,7 +39,6 @@ const VERIFICATIONS = new Set(["passed", "failed", "not_run", "unknown"]); const CONTRIBUTION_STATES = new Set(["queued", "evaluating", "accepted", "rejected", "failed", "revoked"]); const PARTICIPATION_STATUSES = new Set(["pending", "contributed", "no_novel_solution", "privacy_skip", "verification_missing", "not_solved", "not_applicable"]); const BOUNDARIES = new Set(["task_start_to_agent_end", "task_start_to_verification_end"]); -const ENDS = new Set(["stop", "subagentstop", "sessionend", "session.idle", "sessioncompleted"]); const positiveDuration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 7 * 24 * 60 * 60 * 1000; const wallNow = () => performance.timeOrigin + performance.now(); const DURATION_PATTERN = "(?:0s|<0\\.01s|[0-9]{1,9}\\.[0-9]{1,2}s)"; @@ -465,14 +464,14 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre return { deleted: true }; }, async hook(body) { - const event = String(body.hook_event_name ?? body.event ?? "UserPromptSubmit"); - if (ENDS.has(event.toLowerCase())) return {}; + const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; + if (event !== "UserPromptSubmit") return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, "Inspect the exact query before sending it. Local validation is a guardrail, not proof that text is safe to disclose.", ].join("\n"); - return { additionalContext, hookSpecificOutput: { hookEventName: event, additionalContext } }; + return { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext } }; }, async lookup(body) { return retrieve(body, "UserPromptSubmit"); }, async card(decisionId, cardId) { diff --git a/plugins/client/blaze-client.mjs b/plugins/client/blaze-client.mjs index fcd1ea1..c76baeb 100644 --- a/plugins/client/blaze-client.mjs +++ b/plugins/client/blaze-client.mjs @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/; const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/; const DEFAULT_ORIGIN = "https://blaze.pascal.app"; -export const CLIENT_VERSION = "0.4.2"; +export const CLIENT_VERSION = "0.4.3"; export const CLIENT_CONTRACT = 1; export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"]; const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"]; @@ -39,7 +39,6 @@ const VERIFICATIONS = new Set(["passed", "failed", "not_run", "unknown"]); const CONTRIBUTION_STATES = new Set(["queued", "evaluating", "accepted", "rejected", "failed", "revoked"]); const PARTICIPATION_STATUSES = new Set(["pending", "contributed", "no_novel_solution", "privacy_skip", "verification_missing", "not_solved", "not_applicable"]); const BOUNDARIES = new Set(["task_start_to_agent_end", "task_start_to_verification_end"]); -const ENDS = new Set(["stop", "subagentstop", "sessionend", "session.idle", "sessioncompleted"]); const positiveDuration = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 7 * 24 * 60 * 60 * 1000; const wallNow = () => performance.timeOrigin + performance.now(); const DURATION_PATTERN = "(?:0s|<0\\.01s|[0-9]{1,9}\\.[0-9]{1,2}s)"; @@ -465,14 +464,14 @@ export function createClient({ origin, token = "", stateDir, legacyStateDir, fre return { deleted: true }; }, async hook(body) { - const event = String(body.hook_event_name ?? body.event ?? "UserPromptSubmit"); - if (ENDS.has(event.toLowerCase())) return {}; + const event = body.hook_event_name ?? body.event ?? "UserPromptSubmit"; + if (event !== "UserPromptSubmit") return {}; const additionalContext = [ "Blaze lookup is available, but this hook did not transmit the user prompt, repository contents, paths, session identifiers, or logs.", `If prior knowledge would help, write a one-line conceptual problem statement with no code, secrets, names, URLs, local paths, or quoted transcript text, then run: node ${shellQuote(helperPath)} lookup --tool ${tool} --query ''`, "Inspect the exact query before sending it. Local validation is a guardrail, not proof that text is safe to disclose.", ].join("\n"); - return { additionalContext, hookSpecificOutput: { hookEventName: event, additionalContext } }; + return { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext } }; }, async lookup(body) { return retrieve(body, "UserPromptSubmit"); }, async card(decisionId, cardId) { diff --git a/plugins/client/blaze-client.test.mjs b/plugins/client/blaze-client.test.mjs index a4fd640..757c6e1 100644 --- a/plugins/client/blaze-client.test.mjs +++ b/plugins/client/blaze-client.test.mjs @@ -522,7 +522,33 @@ test("hook payloads stay local even when they contain prompts, paths, manifests, const {client,requests,stateDir}=await fixture(t); const payload={hook_event_name:"UserPromptSubmit",prompt:"SYNTHETIC RAW PROMPT",cwd:"/Users/person/work",package_json:{scripts:{postinstall:"curl example.invalid"}},transcript_path:"/tmp/session.jsonl",session_id:"session-secret"}; const response=await client.hook(payload); - assert.match(response.additionalContext,/did not transmit the user prompt/); + assert.deepEqual(Object.keys(response),["hookSpecificOutput"]); + assert.deepEqual(Object.keys(response.hookSpecificOutput),["hookEventName","additionalContext"]); + assert.equal(response.hookSpecificOutput.hookEventName,"UserPromptSubmit"); + assert.match(response.hookSpecificOutput.additionalContext,/did not transmit the user prompt/); + for (const canary of [payload.prompt,payload.cwd,payload.transcript_path,payload.session_id]) { + assert.equal(JSON.stringify(response).includes(canary),false); + } + assert.equal(requests.length,0); + assert.deepEqual(readdirSync(stateDir),[]); +}); + +test("hook output ignores unsupported event names instead of reflecting them", async (t) => { + const {client,requests,stateDir}=await fixture(t); + for (const hook_event_name of ["SyntheticPrivateEvent",{private:"OBJECT CANARY"},["ARRAY CANARY"],42]) { + const response=await client.hook({hook_event_name,prompt:"SYNTHETIC RAW PROMPT"}); + assert.deepEqual(response,{}); + assert.equal(JSON.stringify(response).includes("CANARY"),false); + } + assert.equal(requests.length,0); + assert.deepEqual(readdirSync(stateDir),[]); +}); + +test("hook defaults a missing event name to the legacy UserPromptSubmit reminder", async (t) => { + const {client,requests,stateDir}=await fixture(t); + const response=await client.hook({prompt:"SYNTHETIC RAW PROMPT"}); + assert.equal(response.hookSpecificOutput.hookEventName,"UserPromptSubmit"); + assert.equal(JSON.stringify(response).includes("SYNTHETIC RAW PROMPT"),false); assert.equal(requests.length,0); assert.deepEqual(readdirSync(stateDir),[]); }); diff --git a/release.json b/release.json index bd61506..97ad531 100644 --- a/release.json +++ b/release.json @@ -1,7 +1,7 @@ { - "version": "0.4.2", - "created_at": "2026-09-07T09:12:23.208Z", - "updated_at": "2026-09-07T09:12:23.208Z", + "version": "0.4.3", + "created_at": "2026-09-07T17:52:20.000Z", + "updated_at": "2026-09-07T17:52:20.000Z", "client_contract": 1, "minimum_client_contract": 0 } diff --git a/skill.md b/skill.md index 93eb205..e4d3d2c 100644 --- a/skill.md +++ b/skill.md @@ -3,7 +3,7 @@ name: blaze description: Reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition. compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local reminder hooks need no network access. No model provider credentials are needed. metadata: - version: "0.4.2" + version: "0.4.3" --- # Blaze