claude-mem 13.27.0 → 13.27.1
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/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/npx-cli/index.js +1 -1
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/skills/agent-cost-report/CHECKSUMS.txt +6 -6
- package/openclaw/skills/agent-cost-report/SKILL.md +1 -0
- package/openclaw/skills/agent-cost-report/scripts/acr/render.py +5 -1
- package/openclaw/skills/agent-cost-report/scripts/acr/rollup.py +3 -3
- package/openclaw/skills/agent-cost-report/scripts/acr/wins.py +64 -4
- package/openclaw/skills/agent-cost-report/scripts/acr.py +2 -1
- package/openclaw/skills/agent-cost-report/scripts/tests/test_wins.py +44 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/plugin/scripts/context-generator.cjs +17 -15
- package/plugin/scripts/context-generator.cjs.map +2 -2
- package/plugin/scripts/mcp-server.cjs +1 -1
- package/plugin/scripts/server-service.cjs +2 -2
- package/plugin/scripts/transcript-watcher.cjs +1 -1
- package/plugin/scripts/worker-service.cjs +214 -212
- package/plugin/scripts/worker-service.cjs.map +2 -2
- package/plugin/skills/agent-cost-report/CHECKSUMS.txt +6 -6
- package/plugin/skills/agent-cost-report/SKILL.md +1 -0
- package/plugin/skills/agent-cost-report/scripts/acr/render.py +5 -1
- package/plugin/skills/agent-cost-report/scripts/acr/rollup.py +3 -3
- package/plugin/skills/agent-cost-report/scripts/acr/wins.py +64 -4
- package/plugin/skills/agent-cost-report/scripts/acr.py +2 -1
- package/plugin/skills/agent-cost-report/scripts/tests/test_wins.py +44 -2
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
|
|
60
60
|
</a>
|
|
61
61
|
<a href="package.json">
|
|
62
|
-
<img src="https://img.shields.io/badge/version-13.
|
|
62
|
+
<img src="https://img.shields.io/badge/version-13.27.1-green.svg" alt="Version">
|
|
63
63
|
</a>
|
|
64
64
|
<a href="package.json">
|
|
65
65
|
<img src="https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg" alt="Node">
|
package/dist/npx-cli/index.js
CHANGED
|
@@ -100,7 +100,7 @@ ${o.stack}`:` ${o.message}`;else if(this.getLevel()===0&&typeof o=="object")try{
|
|
|
100
100
|
${m.stack??""}
|
|
101
101
|
`)}else $n(f+`
|
|
102
102
|
`)}debug(e,r,i,n){this.log(0,e,r,i,n)}info(e,r,i,n){this.log(1,e,r,i,n)}warn(e,r,i,n){this.log(2,e,r,i,n)}setErrorSink(e){qS=e}error(e,r,i,n){this.log(3,e,r,i,n),this.routeErrorToSink(r,i,n)}routeErrorToSink(e,r,i){try{if(!qS||!(i instanceof Error))return;qS(i)}catch{}}dataIn(e,r,i,n){this.info(e,`\u2192 ${r}`,i,n)}dataOut(e,r,i,n){this.info(e,`\u2190 ${r}`,i,n)}success(e,r,i,n){this.info(e,`\u2713 ${r}`,i,n)}failure(e,r,i,n){this.error(e,`\u2717 ${r}`,i,n)}},w=new BS});import{join as UP}from"path";import{mkdirSync as N8,writeFileSync as L8}from"fs";import{randomUUID as $8}from"crypto";function U8(t){let e=t.DO_NOT_TRACK;return e===void 0||e===""?!1:e!=="0"&&e!=="false"}function VS(t,e){if(U8(t))return{enabled:!1,source:"DO_NOT_TRACK"};let r=t.CLAUDE_MEM_TELEMETRY?.toLowerCase();return r==="0"||r==="false"||r==="off"?{enabled:!1,source:"env"}:r==="1"||r==="true"||r==="on"?{enabled:!0,source:"env"}:e?.enabled===!0?{enabled:!0,source:"config"}:e?.enabled===!1?{enabled:!1,source:"config"}:{enabled:!0,source:"default"}}function JP(t,e){return VS(t,e).enabled}function eu(){return UP(Un(),FP)}function po(){let t;try{t=lr(eu(),null)}catch(e){let r=e instanceof Error?e:new Error(String(e));return w.warn("SYSTEM","Telemetry: corrupt telemetry.json; treating as no recorded consent",void 0,r),null}return!t||typeof t!="object"||typeof t.installId!="string"||t.enabled!==void 0&&typeof t.enabled!="boolean"?null:{enabled:t.enabled,installId:t.installId,decidedAt:typeof t.decidedAt=="string"?t.decidedAt:""}}function Fa(t){let e=Un();N8(e,{recursive:!0}),L8(UP(e,FP),JSON.stringify(t,null,2)+`
|
|
103
|
-
`)}function tf(){let t=po();if(t?.installId)return t.installId;let e=$8();return Fa({installId:e,decidedAt:""}),e}var FP,rf=E(()=>{"use strict";He();ls();Oe();FP="telemetry.json"});import F8 from"os";function J8(t){try{if(typeof t!="string"){if(t==null)return"";try{t=String(t)}catch(r){let i=r instanceof Error?r:new Error(String(r));return w.warn("SYSTEM","error-scrub: String() coercion of non-string input failed",void 0,i),""}}let e=t;return e.length>zP?e.slice(0,zP):e}catch{return""}}function z8(t){if(typeof t!="string"||t.length===0)return t??"";let e="";try{e=F8.homedir()||""}catch(i){let n=i instanceof Error?i:new Error(String(i));w.warn("SYSTEM","error-scrub: os.homedir() failed; skipping home-dir redaction",void 0,n),e=""}if(!e)return t;let r=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");try{return t.replace(new RegExp(r,"g"),"~")}catch(i){let n=i instanceof Error?i:new Error(String(i));return w.warn("SYSTEM","error-scrub: home-dir RegExp replace failed; using split/join fallback",void 0,n),t.split(e).join("~")}}function q8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/(?<![~\w])\/(?:[^\s/:"'()]+\/)+([^\s/:"'()]+)/g,"$1").replace(/[A-Za-z]:\\(?:[^\s\\:"'()]+\\)*([^\s\\:"'()]+)/g,"$1").replace(/\\\\[^\s\\:"'()]+(?:\\[^\s\\:"'()]+)*\\([^\s\\:"'()]+)/g,"$1")}function G8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s"'()]+/g,e=>/^https?:\/\/hooks\.slack\.com\/services\//i.test(e)?e.replace(/^(https?:\/\/hooks\.slack\.com\/services\/)[^\s"'()]+/i,`$1${mn}`):e.replace(/[?#].*$/,"").replace(/^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^/@\s]+@/,`$1${mn}@`))}function B8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/\?[^\s"'()]*/g,"")}function V8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/\b(?:access[_-]?token|api[_-]?key|auth(?:orization)?|secret|password|passwd|jwt|session[_-]?id|token)=[^\s"'&]+/gi,e=>{let r=e.indexOf("=");return`${e.slice(0,r+1)}${mn}`})}function W8(t){if(typeof t!="string"||t.length===0)return t??"";let e=t;return e=e.replace(/[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,255}\.[A-Za-z]{2,24}/g,mn),e=e.replace(/\b[A-Za-z0-9_-]{10,512}\.[A-Za-z0-9_-]{10,512}\.[A-Za-z0-9_-]{10,512}\b/g,mn),e=e.replace(/\b(?:sk|pk|rk|ak|phc|phx|ph|ghp|gho|ghs|xox[bpasr])[-_][A-Za-z0-9_-]{8,512}\b/gi,mn),e=e.replace(/\bBearer\s+[A-Za-z0-9._-]{8,512}\b/gi,mn),e=e.replace(/\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA|AIPA)[0-9A-Z]{16}\b/g,mn),e=e.replace(/\b[0-9a-fA-F]{24,4096}\b/g,mn),e=e.replace(/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g,mn),e=e.replace(/\b(?=[A-Za-z0-9+/_-]{0,4096}\d)[A-Za-z0-9+/_-]{32,4096}={0,2}\b/g,mn),e=e.replace(/\b(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}\b/g,mn),e}function qP(t){let e=J8(t);return e.length===0?"":(e=z8(e),e=G8(e),e=q8(e),e=B8(e),e=V8(e),e=W8(e),e)}var zP,mn,GP=E(()=>{"use strict";Oe();zP=8192,mn="[REDACTED]"});function Z8(t,e){if(!(!t||typeof t!="object"))for(let r of Object.keys(t)){if(!H8.has(r))continue;let i=t[r];if(typeof i=="string"){let n=i.length>BP?i.slice(0,BP):i;e[r]=qP(n)}else(typeof i=="number"&&Number.isFinite(i)||typeof i=="boolean")&&(e[r]=i)}}function VP(t){let e={};try{Z8(t,e)}catch{}return e}var H8,BP,WP=E(()=>{"use strict";GP();H8=new Set(["version","os","os_version","is_wsl","arch","runtime","runtime_version","node_version","duration_ms","outcome","error_category","locale","is_ci","endpoint","ide","provider","runtime_mode","trigger","count","has_summary","is_update","stage","install_method","interactive","bun_version","uv_version","claude_code_version","trial_days","trial_variant","offer_surface","funnel_source","observation_count","session_count","timeline_depth_days","has_session_summary","obs_type_bugfix","obs_type_discovery","obs_type_decision","obs_type_refactor","obs_type_other","tokens_injected","tokens_saved_vs_naive","mode","search_strategy","observation_type","hook","compression_ms","tokens_input","tokens_output","compression_ratio","model","cost_usd","endpoint_class","db_observation_count","db_session_count","db_summary_count","db_project_count","db_size_mb","install_age_days","obs_count_7d","obs_count_30d","days_since_last_obs","result_count","chroma_available","fallback_reason","invalid_output_class","consecutive_invalid_outputs","respawn_triggered","abort_reason","previous_shutdown","previous_uptime_seconds","uptime_seconds","shutdown_reason","process_rss_mb","heap_used_mb","hook_type","error_mode","consecutive_failures","threshold_tripped","limit_window","overage_status","is_using_overage","resets_in_minutes","discovery_tokens","read_tokens","summary_count","prompt_count","project_count","backfilled","first_active_date","session_completed_count","session_failed_count","sessions_claude_count","sessions_codex_count","sessions_gemini_count","sessions_other_platform_count","subagent_obs_count","total_tokens_input","total_tokens_output","total_cost_usd","avg_duration_ms","avg_compression_ms","outcomes_ok","outcomes_error","outcomes_aborted","outcomes_invalid_output","top_model","observed_model","observed_billing","window_start_ts","rollup_reason","window_seq","total_tokens","avg_tokens","skill_id","skill_source","skill_trigger","observations_created","total_observations_injected","total_tokens_saved_vs_naive"]),BP=200});import HP from"os";function ZP(){return process.env.CLAUDE_MEM_TELEMETRY_KEY||X8}function YP(){return process.env.CLAUDE_MEM_TELEMETRY_HOST||Q8}function XP(t){let e={};for(let r of eX)t[r]!==void 0&&(e[r]=t[r]);return e}function tX(){if(process.platform!=="linux")return!1;try{return!!process.env.WSL_DISTRO_NAME||HP.release().toLowerCase().includes("microsoft")}catch(t){let e=t instanceof Error?t:new Error(String(t));return w.warn("SYSTEM","Telemetry: WSL detection failed; reporting is_wsl=false",void 0,e),!1}}function QP(){return{version:Y8,os:process.platform,os_version:HP.release(),is_wsl:tX(),arch:process.arch,runtime:process.versions.bun?"bun":"node",runtime_version:process.versions.bun??process.versions.node,node_version:process.versions.node,is_ci:!!process.env.CI,locale:Intl.DateTimeFormat().resolvedOptions().locale}}var Y8,X8,Q8,eX,eM=E(()=>{"use strict";Oe();Y8="13.27.
|
|
103
|
+
`)}function tf(){let t=po();if(t?.installId)return t.installId;let e=$8();return Fa({installId:e,decidedAt:""}),e}var FP,rf=E(()=>{"use strict";He();ls();Oe();FP="telemetry.json"});import F8 from"os";function J8(t){try{if(typeof t!="string"){if(t==null)return"";try{t=String(t)}catch(r){let i=r instanceof Error?r:new Error(String(r));return w.warn("SYSTEM","error-scrub: String() coercion of non-string input failed",void 0,i),""}}let e=t;return e.length>zP?e.slice(0,zP):e}catch{return""}}function z8(t){if(typeof t!="string"||t.length===0)return t??"";let e="";try{e=F8.homedir()||""}catch(i){let n=i instanceof Error?i:new Error(String(i));w.warn("SYSTEM","error-scrub: os.homedir() failed; skipping home-dir redaction",void 0,n),e=""}if(!e)return t;let r=e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");try{return t.replace(new RegExp(r,"g"),"~")}catch(i){let n=i instanceof Error?i:new Error(String(i));return w.warn("SYSTEM","error-scrub: home-dir RegExp replace failed; using split/join fallback",void 0,n),t.split(e).join("~")}}function q8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/(?<![~\w])\/(?:[^\s/:"'()]+\/)+([^\s/:"'()]+)/g,"$1").replace(/[A-Za-z]:\\(?:[^\s\\:"'()]+\\)*([^\s\\:"'()]+)/g,"$1").replace(/\\\\[^\s\\:"'()]+(?:\\[^\s\\:"'()]+)*\\([^\s\\:"'()]+)/g,"$1")}function G8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s"'()]+/g,e=>/^https?:\/\/hooks\.slack\.com\/services\//i.test(e)?e.replace(/^(https?:\/\/hooks\.slack\.com\/services\/)[^\s"'()]+/i,`$1${mn}`):e.replace(/[?#].*$/,"").replace(/^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^/@\s]+@/,`$1${mn}@`))}function B8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/\?[^\s"'()]*/g,"")}function V8(t){return typeof t!="string"||t.length===0?t??"":t.replace(/\b(?:access[_-]?token|api[_-]?key|auth(?:orization)?|secret|password|passwd|jwt|session[_-]?id|token)=[^\s"'&]+/gi,e=>{let r=e.indexOf("=");return`${e.slice(0,r+1)}${mn}`})}function W8(t){if(typeof t!="string"||t.length===0)return t??"";let e=t;return e=e.replace(/[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,255}\.[A-Za-z]{2,24}/g,mn),e=e.replace(/\b[A-Za-z0-9_-]{10,512}\.[A-Za-z0-9_-]{10,512}\.[A-Za-z0-9_-]{10,512}\b/g,mn),e=e.replace(/\b(?:sk|pk|rk|ak|phc|phx|ph|ghp|gho|ghs|xox[bpasr])[-_][A-Za-z0-9_-]{8,512}\b/gi,mn),e=e.replace(/\bBearer\s+[A-Za-z0-9._-]{8,512}\b/gi,mn),e=e.replace(/\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA|AIPA)[0-9A-Z]{16}\b/g,mn),e=e.replace(/\b[0-9a-fA-F]{24,4096}\b/g,mn),e=e.replace(/\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/g,mn),e=e.replace(/\b(?=[A-Za-z0-9+/_-]{0,4096}\d)[A-Za-z0-9+/_-]{32,4096}={0,2}\b/g,mn),e=e.replace(/\b(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}\b/g,mn),e}function qP(t){let e=J8(t);return e.length===0?"":(e=z8(e),e=G8(e),e=q8(e),e=B8(e),e=V8(e),e=W8(e),e)}var zP,mn,GP=E(()=>{"use strict";Oe();zP=8192,mn="[REDACTED]"});function Z8(t,e){if(!(!t||typeof t!="object"))for(let r of Object.keys(t)){if(!H8.has(r))continue;let i=t[r];if(typeof i=="string"){let n=i.length>BP?i.slice(0,BP):i;e[r]=qP(n)}else(typeof i=="number"&&Number.isFinite(i)||typeof i=="boolean")&&(e[r]=i)}}function VP(t){let e={};try{Z8(t,e)}catch{}return e}var H8,BP,WP=E(()=>{"use strict";GP();H8=new Set(["version","os","os_version","is_wsl","arch","runtime","runtime_version","node_version","duration_ms","outcome","error_category","locale","is_ci","endpoint","ide","provider","runtime_mode","trigger","count","has_summary","is_update","stage","install_method","interactive","bun_version","uv_version","claude_code_version","trial_days","trial_variant","offer_surface","funnel_source","observation_count","session_count","timeline_depth_days","has_session_summary","obs_type_bugfix","obs_type_discovery","obs_type_decision","obs_type_refactor","obs_type_other","tokens_injected","tokens_saved_vs_naive","mode","search_strategy","observation_type","hook","compression_ms","tokens_input","tokens_output","compression_ratio","model","cost_usd","endpoint_class","db_observation_count","db_session_count","db_summary_count","db_project_count","db_size_mb","install_age_days","obs_count_7d","obs_count_30d","days_since_last_obs","result_count","chroma_available","fallback_reason","invalid_output_class","consecutive_invalid_outputs","respawn_triggered","abort_reason","previous_shutdown","previous_uptime_seconds","uptime_seconds","shutdown_reason","process_rss_mb","heap_used_mb","hook_type","error_mode","consecutive_failures","threshold_tripped","limit_window","overage_status","is_using_overage","resets_in_minutes","discovery_tokens","read_tokens","summary_count","prompt_count","project_count","backfilled","first_active_date","session_completed_count","session_failed_count","sessions_claude_count","sessions_codex_count","sessions_gemini_count","sessions_other_platform_count","subagent_obs_count","total_tokens_input","total_tokens_output","total_cost_usd","avg_duration_ms","avg_compression_ms","outcomes_ok","outcomes_error","outcomes_aborted","outcomes_invalid_output","top_model","observed_model","observed_billing","window_start_ts","rollup_reason","window_seq","total_tokens","avg_tokens","skill_id","skill_source","skill_trigger","observations_created","total_observations_injected","total_tokens_saved_vs_naive"]),BP=200});import HP from"os";function ZP(){return process.env.CLAUDE_MEM_TELEMETRY_KEY||X8}function YP(){return process.env.CLAUDE_MEM_TELEMETRY_HOST||Q8}function XP(t){let e={};for(let r of eX)t[r]!==void 0&&(e[r]=t[r]);return e}function tX(){if(process.platform!=="linux")return!1;try{return!!process.env.WSL_DISTRO_NAME||HP.release().toLowerCase().includes("microsoft")}catch(t){let e=t instanceof Error?t:new Error(String(t));return w.warn("SYSTEM","Telemetry: WSL detection failed; reporting is_wsl=false",void 0,e),!1}}function QP(){return{version:Y8,os:process.platform,os_version:HP.release(),is_wsl:tX(),arch:process.arch,runtime:process.versions.bun?"bun":"node",runtime_version:process.versions.bun??process.versions.node,node_version:process.versions.node,is_ci:!!process.env.CI,locale:Intl.DateTimeFormat().resolvedOptions().locale}}var Y8,X8,Q8,eX,eM=E(()=>{"use strict";Oe();Y8="13.27.1",X8="phc_BKJAeNbpj932N9qEiU6qhutZEiu6LLfRpXfTbLM9MLaG",Q8="https://us.i.posthog.com";eX=["version","os","os_version","is_wsl","arch","runtime","locale","ide","provider","runtime_mode","install_method","claude_code_version","first_active_date","db_observation_count","db_session_count","db_summary_count","db_project_count","db_size_mb","install_age_days","obs_count_7d","obs_count_30d","days_since_last_obs"]});async function oi(t,e,r){try{if(!JP(process.env,po()))return;let i=VP({...QP(),...e??{}});if(r?.person?i.$set=XP(i):i.$process_person_profile=!1,process.env.CLAUDE_MEM_TELEMETRY_DEBUG==="1"){process.stderr.write("[telemetry] "+JSON.stringify({event:t,properties:i})+`
|
|
104
104
|
`);return}let n=ZP(),o=new AbortController,s=setTimeout(()=>o.abort(),rX);try{await nX(n,t,i,o.signal)}finally{clearTimeout(s)}}catch{}}async function nX(t,e,r,i){await fetch(`${YP()}/capture/`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({api_key:t,event:e,distinct_id:tf(),properties:r}),signal:i})}var rX,nf=E(()=>{"use strict";rf();WP();eM();rX=2e3});import{spawn as iX,spawnSync as tM}from"node:child_process";import{dirname as oX,extname as tu,join as sX}from"node:path";function si(t,e,r){return iX(t,e??[],{windowsHide:!0,...r})}function lX(t){return`"${t.replace(/"/g,'""')}"`}function ZS(t){if(process.platform!=="win32")return[];try{let e=tM("where",[t],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],windowsHide:!0});return e.status!==0||!e.stdout.trim()?[]:e.stdout.split(/\r?\n/).map(r=>r.trim()).filter(Boolean)}catch{return[]}}function dX(t,e){let r=t.find(n=>WS.has(tu(n).toLowerCase()));if(r)return r;let i=t.find(n=>HS.has(tu(n).toLowerCase()));if(i&&e){let n=e(i);if(n&&WS.has(tu(n).toLowerCase()))return n}return i??t.find(n=>aX.has(tu(n).toLowerCase()))??t[0]??null}function pX(t,e,r=uX){if(!/[\\/]volta[\\/]bin[\\/]/i.test(e))return null;try{let i=r(sX(oX(e),"volta.exe"),["which",t],{encoding:"utf-8",stdio:["ignore","pipe","ignore"],timeout:cX,windowsHide:!0});return i.status!==0||!i.stdout.trim()?null:i.stdout.split(/\r?\n/).map(n=>n.trim()).find(Boolean)??null}catch{return null}}function Ja(t){return dX(ZS(t),e=>pX(t,e))}function za(t,e,r,i=process.platform){let n={...i==="win32"?{windowsHide:!0}:{},...r};if(i==="win32"&&HS.has(tu(t).toLowerCase())){let o=[t,...e].map(lX).join(" ");return{command:process.env.ComSpec??"cmd.exe",args:["/d","/s","/c",`"${o}"`],options:{...n,windowsVerbatimArguments:!0}}}return{command:t,args:[...e],options:n}}var HS,WS,aX,cX,uX,ai=E(()=>{"use strict";HS=new Set([".cmd",".bat"]),WS=new Set([".exe",".com"]),aX=new Set([...WS,...HS]);cX=5e3,uX=(t,e,r)=>tM(t,e,r)});function qa(t){return process.platform==="win32"?Math.round(t*qr.WINDOWS_MULTIPLIER):t}var qr,of=E(()=>{"use strict";qr={HEALTH_CHECK:3e3,API_REQUEST:3e4,HOOK_READINESS_WAIT:1e4,POST_SPAWN_WAIT:15e3,READINESS_WAIT:3e4,PORT_IN_USE_WAIT:3e3,POWERSHELL_COMMAND:1e4,WINDOWS_MULTIPLIER:1.5}});var iM={};De(iM,{SettingsDefaultsManager:()=>Le});import{readFileSync as fX,existsSync as mX}from"fs";import{join as rM}from"path";import{homedir as nM,hostname as hX}from"os";function vX(t){if(typeof t!="string")return null;let e=t.trim();if(e.length===0)return null;try{if(new URL(e).hostname===gX)return bX}catch{return null}return null}var yX,gX,bX,Le,Dr=E(()=>{"use strict";of();Rr();yX="security_alert",gX="sync-hub.black-pond-afbb.workers.dev",bX="https://sync.cmem.ai";Le=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-haiku-4-5-20251001",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:String(37700+(process.getuid?.()??77)%100),CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_API_TIMEOUT_MS:String(qa(qr.API_REQUEST)),CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_CLAUDE_AUTH_METHOD:"subscription",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-flash-latest",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_BASE_URL:"",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_DATA_DIR:rM(nM(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_CLAUDE_CONFIG_DIR:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"false",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"false",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"false",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:"",CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:"",CLAUDE_MEM_CONTEXT_FULL_COUNT:"0",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false",CLAUDE_MEM_CONTEXT_SHOW_TERMINAL_OUTPUT:"true",CLAUDE_MEM_WELCOME_HINT_ENABLED:"true",CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED:"false",CLAUDE_MEM_FOLDER_USE_LOCAL_MD:"false",CLAUDE_MEM_TRANSCRIPTS_ENABLED:"true",CLAUDE_MEM_TRANSCRIPTS_CONFIG_PATH:rM(nM(),".claude-mem","transcript-watch.json"),CLAUDE_MEM_CODEX_TRANSCRIPT_INGESTION:"false",CLAUDE_MEM_MAX_CONCURRENT_AGENTS:"2",CLAUDE_MEM_OBSERVER_MAX_CONVERSATION_CHARS:"400000",CLAUDE_MEM_HOOK_FAIL_LOUD_THRESHOLD:"3",CLAUDE_MEM_EXCLUDED_PROJECTS:"",CLAUDE_MEM_FOLDER_MD_EXCLUDE:"[]",CLAUDE_MEM_FOLDER_MD_SKELETON_DENYLIST:"[]",CLAUDE_MEM_SEMANTIC_INJECT:"false",CLAUDE_MEM_SEMANTIC_INJECT_LIMIT:"5",CLAUDE_MEM_TIER_ROUTING_ENABLED:"true",CLAUDE_MEM_TIER_SIMPLE_MODEL:"haiku",CLAUDE_MEM_TIER_SUMMARY_MODEL:"",CLAUDE_MEM_TIER_FAST_MODEL:"haiku",CLAUDE_MEM_TIER_SMART_MODEL:"sonnet",CLAUDE_MEM_CHROMA_ENABLED:"true",CLAUDE_MEM_CHROMA_MODE:"local",CLAUDE_MEM_CHROMA_HOST:"127.0.0.1",CLAUDE_MEM_CHROMA_PORT:"8000",CLAUDE_MEM_CHROMA_SSL:"false",CLAUDE_MEM_CHROMA_API_KEY:"",CLAUDE_MEM_CHROMA_TENANT:"default_tenant",CLAUDE_MEM_CHROMA_DATABASE:"default_database",CLAUDE_MEM_CHROMA_PREWARM_TIMEOUT_MS:"120000",CLAUDE_MEM_CHROMA_MAX_PENDING_MUTATIONS:"5000",CLAUDE_MEM_CLOUD_SYNC_TOKEN:"",CLAUDE_MEM_CLOUD_SYNC_USER_ID:"",CLAUDE_MEM_CLOUD_SYNC_HUB_URL:"",CLAUDE_MEM_CLOUD_SYNC_DEVICE_ID:"",CLAUDE_MEM_CLOUD_SYNC_DEVICE_NAME:hX(),CLAUDE_MEM_CLOUD_SYNC_WS:"true",CLAUDE_MEM_CLOUD_SYNC_CONTENT_BATCH_SIZE:"40",CLAUDE_MEM_CLOUD_SYNC_REQUEST_TIMEOUT_MS:"90000",CLAUDE_MEM_LLM_TIMEOUT_MS:"30000",CLAUDE_MEM_TV_TOKEN:"",CLAUDE_MEM_PRO_TRIAL_EMAIL:"",CLAUDE_MEM_PRO_TRIAL_AT:"",CLAUDE_MEM_PRO_TRIAL_STATE:"",CLAUDE_MEM_PRO_TRIAL_ENDS_AT:"",CLAUDE_MEM_PRO_PLAN:"",CLAUDE_MEM_PRO_FALLBACK_AT:"",CLAUDE_MEM_PRO_MEMORY_KEY:"",CLAUDE_MEM_PRO_MEMORY_BASE_URL:"",CLAUDE_MEM_PRO_MEMORY_MODEL:"",CLAUDE_MEM_TELEGRAM_ENABLED:"true",CLAUDE_MEM_TELEGRAM_BOT_TOKEN:"",CLAUDE_MEM_TELEGRAM_CHAT_ID:"",CLAUDE_MEM_TELEGRAM_WRAPUPS_ENABLED:"true",CLAUDE_MEM_TELEGRAM_OBSERVATION_ALERTS_ENABLED:"false",CLAUDE_MEM_TELEGRAM_WRAPUP_ROUTES:"{}",CLAUDE_MEM_TELEGRAM_TRIGGER_TYPES:"security_alert,sensitive",CLAUDE_MEM_TELEGRAM_TRIGGER_CONCEPTS:"",CLAUDE_MEM_GROK_BOT_AWARENESS_ENABLED:"true",CLAUDE_MEM_GROK_BOT_AWARENESS_AGENT_IDS:"521e962d-2ec3-4488-bfbc-54d5209ce118,95601360-61f7-4fd9-bb3a-2c976b2b85c0",CLAUDE_MEM_GROK_BOT_AWARENESS_TRIGGER_TYPES:"decision,bugfix,security_alert,sensitive",CLAUDE_MEM_GROK_BOT_AWARENESS_TRIGGER_CONCEPTS:"",CLAUDE_MEM_GROK_BOT_INJECT_ENABLED:"true",CLAUDE_MEM_GROK_BOT_INJECT_AGENT_IDS:"*",CLAUDE_MEM_GROK_BOT_INJECT_TIER:"episode",CLAUDE_MEM_GROK_BOT_INJECT_WINDOW:"80",CLAUDE_MEM_GROK_BOT_INJECT_FALLBACK:"house",CLAUDE_MEM_GROK_BOT_INJECT_PLATFORM_SOURCE:"",CLAUDE_MEM_GROK_BOT_INJECT_PROJECTS_BY_AGENT:"",CLAUDE_MEM_GROK_BOT_INJECT_MAX_LINE_CHARS:"160",CLAUDE_MEM_GROK_BOT_INJECT_DEBOUNCE_MS:"1500",CLAUDE_MEM_GROK_BOT_INJECT_STANDING_LINE:"",CLAUDE_MEM_CCS_ALIGN_ENABLED:"true",CLAUDE_MEM_CCS_ALIGN_VIEWER_IDS:"ccs-align",CLAUDE_MEM_CCS_ALIGN_TRIGGER_TYPES:"decision,bugfix,security_alert,sensitive",CLAUDE_MEM_CCS_ALIGN_PATCH_SHADOWS:"false",CLAUDE_MEM_QUEUE_ENGINE:"sqlite",CLAUDE_MEM_REDIS_URL:"",CLAUDE_MEM_REDIS_HOST:"127.0.0.1",CLAUDE_MEM_REDIS_PORT:"6379",CLAUDE_MEM_REDIS_MODE:"external",CLAUDE_MEM_QUEUE_REDIS_PREFIX:`claude_mem_${process.env.CLAUDE_MEM_WORKER_PORT??String(37700+(process.getuid?.()??77)%100)}`,CLAUDE_MEM_AUTH_MODE:"api-key",CLAUDE_MEM_RUNTIME:"worker",CLAUDE_MEM_SERVER_URL:`http://127.0.0.1:${process.env.CLAUDE_MEM_SERVER_PORT??String(37877+(process.getuid?.()??77)%100)}`,CLAUDE_MEM_SERVER_API_KEY:"",CLAUDE_MEM_SERVER_PROJECT_ID:"",CLAUDE_MEM_SERVER_BETA_URL:`http://127.0.0.1:${process.env.CLAUDE_MEM_SERVER_PORT??String(37877+(process.getuid?.()??77)%100)}`,CLAUDE_MEM_SERVER_BETA_API_KEY:"",CLAUDE_MEM_SERVER_BETA_PROJECT_ID:""};static getAllDefaults(){return{...this.DEFAULTS}}static get(e){let r=process.env[e]??this.DEFAULTS[e];return e==="CLAUDE_MEM_WORKER_HOST"?this.normalizeWorkerHost(r):r}static normalizeWorkerHost(e){return e==="localhost"?"127.0.0.1":e}static finalizeSettings(e,r){let i=r?this.applyEnvOverrides(e):e;return i.CLAUDE_MEM_WORKER_HOST=this.normalizeWorkerHost(i.CLAUDE_MEM_WORKER_HOST),i}static getInt(e){let r=this.get(e);return parseInt(r,10)}static applyEnvOverrides(e){let r={...e};for(let i of Object.keys(this.DEFAULTS))process.env[i]!==void 0&&(r[i]=process.env[i]);return r}static loadFromFile(e,r=!0){try{if(!mX(e)){let u=this.getAllDefaults();try{lt(e,u),console.warn("[SETTINGS] Created settings file with defaults:",e)}catch(d){console.warn("[SETTINGS] Failed to create settings file, using in-memory defaults:",e,d instanceof Error?d.message:String(d))}return this.finalizeSettings(u,r)}let i=fX(e,"utf-8"),n=Et(i),o=n,s=n.env&&typeof n.env=="object"&&!Array.isArray(n.env),a=s&&Object.keys(n).some(u=>u!=="env");if(s&&(o=n.env,!a))try{lt(e,o),console.warn("[SETTINGS] Migrated settings file from nested to flat schema:",e)}catch(u){console.warn("[SETTINGS] Failed to auto-migrate settings file:",e,u instanceof Error?u.message:String(u))}if(o.CLAUDE_MEM_TELEGRAM_TRIGGER_TYPES===yX){o={...o,CLAUDE_MEM_TELEGRAM_TRIGGER_TYPES:this.DEFAULTS.CLAUDE_MEM_TELEGRAM_TRIGGER_TYPES};try{lt(e,a?{...n,env:o}:o),console.warn("[SETTINGS] Migrated Telegram trigger types off the legacy default:",e)}catch(u){console.warn("[SETTINGS] Failed to migrate Telegram trigger types:",e,u instanceof Error?u.message:String(u))}}let l=vX(o.CLAUDE_MEM_CLOUD_SYNC_HUB_URL);if(l!==null){o={...o,CLAUDE_MEM_CLOUD_SYNC_HUB_URL:l};try{lt(e,a?{...n,env:o}:o),console.warn("[SETTINGS] Migrated cloud sync hub URL off the legacy workers.dev host:",e)}catch(u){console.warn("[SETTINGS] Failed to migrate cloud sync hub URL:",e,u instanceof Error?u.message:String(u))}}let c={...this.DEFAULTS};for(let u of Object.keys(this.DEFAULTS))o[u]!==void 0&&(c[u]=o[u]);return this.finalizeSettings(c,r)}catch(i){console.warn("[SETTINGS] Failed to load settings, using defaults:",e,i instanceof Error?i.message:String(i));let n=this.getAllDefaults();return this.finalizeSettings(n,r)}}}});import{execFile as SX}from"child_process";import{promisify as EX}from"util";import{createHash as LIe}from"crypto";import{existsSync as UIe,readFileSync as FIe,writeFileSync as JIe,mkdirSync as zIe,unlinkSync as qIe}from"fs";import{userInfo as BIe}from"os";import{join as WIe}from"path";var XIe,oM=E(()=>{"use strict";He();Dr();Oe();XIe=EX(SX)});import{existsSync as YS,readFileSync as aM,writeFileSync as wX,mkdirSync as _X,chmodSync as sM}from"fs";import{parseEnv as kX}from"util";import{basename as axe}from"path";function lM(){return process.env.CLAUDE_MEM_ENV_FILE??Be.envFile()}function uM(t){return kX(t)}function IX(t){let e=["# claude-mem credentials","# This file stores keys and gateway settings for the claude-mem memory agent","# Edit this file or use claude-mem settings to configure",""];for(let[r,i]of Object.entries(t))if(i){let n=/[\s#=]/.test(i);e.push(`${r}=${n?`"${i}"`:i}`)}return e.join(`
|
|
105
105
|
`)+`
|
|
106
106
|
`}function dM(){let t=lM();if(!YS(t))return{};try{let e=aM(t,"utf-8"),r=uM(e),i={};for(let n of cM)r[n]&&(i[n]=r[n]);return i}catch(e){return w.warn("ENV","Failed to load .env file",{path:t},e instanceof Error?e:new Error(String(e))),{}}}function pM(t){let e=lM(),r={};try{YS(Be.dataDir())||_X(Be.dataDir(),{recursive:!0,mode:448}),sM(Be.dataDir(),448),r=YS(e)?uM(aM(e,"utf-8")):{}}catch(n){let o=n instanceof Error?n:new Error(String(n));throw w.error("ENV","Failed to set up env directory or read existing env",{},o),o}let i={...r};for(let n of cM){let o=t[n];o!==void 0&&(o?i[n]=o:delete i[n])}try{wX(e,IX(i),{encoding:"utf-8",mode:384}),sM(e,384)}catch(n){throw w.error("ENV","Failed to save .env file",{path:e},n instanceof Error?n:new Error(String(n))),n}}var cM,fM=E(()=>{"use strict";Oe();He();Dr();oM();cM=["ANTHROPIC_API_KEY","ANTHROPIC_BASE_URL","ANTHROPIC_AUTH_TOKEN","GEMINI_API_KEY","OPENROUTER_API_KEY"]});function Fn(t=process.env){let e={};for(let[r,i]of Object.entries(t))if(i!==void 0){if(AX.has(r)){e[r]=i;continue}TX.has(r)||xX.some(n=>r.startsWith(n))||(e[r]=i)}return e}var xX,TX,CX,AX,ru=E(()=>{"use strict";xX=["CLAUDECODE_","CLAUDE_CODE_"],TX=new Set(["CLAUDECODE","CLAUDE_CODE_SESSION","CLAUDE_CODE_ENTRYPOINT","MCP_SESSION_ID"]),CX=new Set(["HTTP_PROXY","HTTPS_PROXY","ALL_PROXY","NO_PROXY","http_proxy","https_proxy","all_proxy","no_proxy","npm_config_proxy","npm_config_https_proxy","npm_config_noproxy"]),AX=new Set(["CLAUDE_CODE_OAUTH_TOKEN","CLAUDE_CODE_GIT_BASH_PATH","CLAUDE_CODE_USE_BEDROCK","CLAUDE_CODE_USE_VERTEX","CLAUDE_CODE_USE_FOUNDRY","CLAUDE_CODE_SKIP_BEDROCK_AUTH","CLAUDE_CODE_SKIP_VERTEX_AUTH","CLAUDE_CODE_SKIP_FOUNDRY_AUTH","ANTHROPIC_BEDROCK_BASE_URL","AWS_REGION","AWS_PROFILE","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_SESSION_TOKEN","ANTHROPIC_VERTEX_PROJECT_ID","CLOUD_ML_REGION","GOOGLE_APPLICATION_CREDENTIALS",...CX])});import{spawnSync as hM}from"child_process";import{readFileSync as OX}from"fs";function DX(t){XS+=1;let e=hM("powershell.exe",["-NoProfile","-NonInteractive","-Command",`(Get-CimInstance Win32_Process -Filter "ProcessId=${t}").CreationDate.ToString('yyyyMMddHHmmss.ffffff')`],{encoding:"utf-8",timeout:5e3,windowsHide:!0,env:{...Fn(process.env),LC_ALL:"C",LANG:"C"}});if(e.status===0){let r=e.stdout.trim();return r.length>0?r:null}return null}function PX(t,e=!1){if(!e){let i=mM.get(t);if(i&&Date.now()-i.capturedAtMs<RX)return i.token}let r=null;try{r=DX(t)}catch(i){w.debug("SYSTEM","captureProcessStartToken: powershell CIM lookup failed",{pid:t,error:i instanceof Error?i.message:String(i)}),r=null}return mM.set(t,{token:r,capturedAtMs:Date.now()}),r}function yM(t,e){if(!Number.isInteger(t)||t<=0)return null;if(process.platform==="linux"){XS+=1;try{let r=OX(`/proc/${t}/stat`,"utf-8"),i=r.lastIndexOf(") ");if(i<0)return null;let o=r.slice(i+2).split(" ")[19];return o&&/^\d+$/.test(o)?o:null}catch(r){return w.debug("SYSTEM","captureProcessStartToken: /proc read failed",{pid:t,error:r instanceof Error?r.message:String(r)}),null}}if(process.platform==="win32")return PX(t,e);XS+=1;try{let r=hM("ps",["-p",String(t),"-o","lstart="],{encoding:"utf-8",timeout:2e3,env:{...Fn(process.env),LC_ALL:"C",LANG:"C"}});if(r.status!==0)return null;let i=r.stdout.trim();return i.length>0?i:null}catch(r){return w.debug("SYSTEM","captureProcessStartToken: ps exec failed",{pid:t,error:r instanceof Error?r.message:String(r)}),null}}function fo(t){return yM(t,!1)}function Di(t,e){if(e===null)return!0;let r=yM(t,!0);return r===null?!0:r===e}var RX,mM,XS,QS=E(()=>{"use strict";Oe();ru();RX=5e3,mM=new Map,XS=0});import{execFile as MX}from"child_process";import{readdirSync as KX,readFileSync as jX}from"fs";import{promisify as NX}from"util";async function Jn(t,e={}){let r=e.signalMode==="immediate",i=e.expectedStartToken!==void 0?e.expectedStartToken:fo(t),n=()=>Di(t,i);if(!n()){w.warn("PROCESS","Skipping tree-kill: root PID was reused since it was captured",{pid:t});return}if(w.debug("PROCESS",`Killing process tree rooted at PID ${t}`,{immediate:r}),process.platform==="win32"){try{await eE("taskkill",["/PID",String(t),"/T","/F"],{timeout:5e3,windowsHide:!0})}catch(o){let s=o.code,a=String(o.stderr??""),l=o instanceof Error?o.message:String(o);if(s===LX||$X.test(`${a} ${l}`)){w.debug("PROCESS","taskkill reported the process was already gone",{pid:t});return}throw new sf(t,`taskkill failed for PID ${t} (exit ${String(s)}): ${a.trim()||l}`,{cause:o})}return}try{let o=r?"SIGKILL":"SIGTERM",s=await af(t);for(let c of s){if(!Di(c.pid,c.startToken)){w.debug("PROCESS",`Skipping ${o}: descendant PID was reused since enumeration`,{pid:c.pid,rootPid:t});continue}try{process.kill(c.pid,o)}catch{}}if(n())try{process.kill(t,o)}catch(c){let u=c instanceof Error?c:new Error(String(c)),d=u.code;d!=="ESRCH"&&w.debug("PROCESS",`Failed to ${o} PID ${t}`,{code:d},u)}else w.warn("PROCESS","Skipping root signal: PID was reused during descendant enumeration",{pid:t,signal:o});r||await new Promise(c=>setTimeout(c,500));let a=n()?await af(t):[],l=new Map;for(let c of s)l.set(c.pid,c.startToken);for(let c of a)l.set(c.pid,c.startToken);for(let[c,u]of l){if(!Di(c,u)){w.debug("PROCESS","Skipping SIGKILL: descendant PID was reused since the scan",{pid:c,rootPid:t});continue}try{process.kill(c,"SIGKILL")}catch{}}if(!r&&n())try{process.kill(t,"SIGKILL")}catch{}}catch(o){throw new sf(t,`tree-kill failed for PID ${t}: ${o instanceof Error?o.message:String(o)}`,{cause:o})}}async function UX(){return process.platform==="win32"?zX():process.platform==="linux"?FX():JX()}function FX(){let t=[],e;try{e=KX("/proc")}catch(r){return w.warn("PROCESS","Cannot read /proc \u2014 tree-kill degrades to a single-PID kill",{error:r instanceof Error?r.message:String(r)}),t}for(let r of e){if(!/^\d+$/.test(r))continue;let i;try{i=jX(`/proc/${r}/stat`,"utf-8")}catch{continue}let n=i.lastIndexOf(") ");if(n<0)continue;let o=i.slice(n+2).split(" "),s=Number.parseInt(o[1]??"",10),a=o[19];Number.isFinite(s)&&t.push({pid:Number.parseInt(r,10),ppid:s,startToken:a&&/^\d+$/.test(a)?a:null})}return t}async function JX(){let t;try{t=(await eE("ps",["-eo","pid=,ppid=,lstart="],{timeout:1e4,maxBuffer:33554432,env:{...process.env,LC_ALL:"C",LANG:"C"}})).stdout}catch(r){return w.warn("PROCESS","Cannot enumerate the process table \u2014 tree-kill degrades to a single-PID kill",{error:r instanceof Error?r.message:String(r)}),[]}let e=[];for(let r of t.split(`
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "Claude-Mem (Persistent Memory)",
|
|
4
4
|
"description": "OpenClaw plugin for Claude-Mem. Records observations from embedded runner sessions and streams them to messaging channels.",
|
|
5
5
|
"kind": "memory",
|
|
6
|
-
"version": "13.27.
|
|
6
|
+
"version": "13.27.1",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"author": "thedotmack",
|
|
9
9
|
"homepage": "https://claude-mem.ai",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
a8388ef9d748761e8daddf0d3dd4889d7dc67688c7d7d65bd003becd7131b613 SKILL.md
|
|
2
|
+
8cf92dc443325efa5f2239d47582607ea38830396defee45669fb11fe76fc573 scripts/acr.py
|
|
3
3
|
bc4fc2a17a1b2bd38422000c907ec07bf1deb1008c7b1bfcf45b2e41364da3e4 scripts/acr/__init__.py
|
|
4
4
|
a5a3394119fca6a376843826c29cec40e6edbd3085f22024bfdad27601ff8b85 scripts/acr/behavior.py
|
|
5
5
|
f72d82461e2c02584bcc3f4d7a05fd18165e5b3d255c0333c7763599daa5adaa scripts/acr/classify.py
|
|
@@ -12,15 +12,15 @@ f617e2c75cd65f4415a7ae6107d03681bc85365684ebf777b594f074c87e231c scripts/acr/mi
|
|
|
12
12
|
3ae9141248b97d27a87c7ac9526db0a663f90924448126e4b2cab5c090a4f3a3 scripts/acr/patterns.py
|
|
13
13
|
9bc988081b4e48f252a9b47ddd6f26bf05766bd5e41495736d1f29c3db833b6f scripts/acr/period.py
|
|
14
14
|
5235c50c24012738321cf441d61dd8987be398b5939af8c9fd038f49417c261b scripts/acr/prices.py
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
64b71efe36cdfb846a11680e3b61873110442395004e8dc2e571245634bcc96b scripts/acr/render.py
|
|
16
|
+
652ff78b6e470664817d83b802e452abb08f97175650f7d78855719806307824 scripts/acr/rollup.py
|
|
17
17
|
6637b40b8171127a9d76cf9a971412f6e0ee351246e1944070cd63d36d2bd3fb scripts/acr/rules.json
|
|
18
18
|
2c57f2e41221be84ce08d5146cdff6d5016119c120fc43505afe7274a18d0012 scripts/acr/rules.py
|
|
19
19
|
6328e919ab8e09535129b4f3845fc2e0d0ab50d0beafbc3042f57bee07ba9db2 scripts/acr/sample.py
|
|
20
20
|
d329883df29e52e66312ef0829a17521adb832c4d267a69957aba10235b34679 scripts/acr/snapshot.py
|
|
21
21
|
0d03c8ed1c8076ec59dc8f6c1241ca52f5d2ecf83f47792d8dfb20a1c8737a81 scripts/acr/sync.py
|
|
22
22
|
05155c3c9d0f061484d933839189a7b14d2b56bab603ef41a0e0e141be4c8848 scripts/acr/transcripts.py
|
|
23
|
-
|
|
23
|
+
e9d4a37c31d6ca9d0c403cbe76bb4e2a087d5808cd4d038315bf5750cfe53e5c scripts/acr/wins.py
|
|
24
24
|
34f188a792da085d50a0fd608836ca03395a01978229aad90fb7901be753e655 scripts/tests/_db.py
|
|
25
25
|
1baa143bea04326b7096a0654f91c22255d1f131ed23af454395b9d3c959f78d scripts/tests/_paths.py
|
|
26
26
|
a2cc21046a1e79c3977e5bf22ca75f67c0c77d4e725ef33663ac62b289688e7e scripts/tests/test_behavior.py
|
|
@@ -34,7 +34,7 @@ ccbb9887b02d145359a42cfb3919850d4e4b7b538b9017caa7cc4312a94b85cd scripts/tests/
|
|
|
34
34
|
323379622ffc8984d3bf2d3710fb1cc8284fd7174ed7e6725dbf5613bea5cdd4 scripts/tests/test_snapshot.py
|
|
35
35
|
ca1b30d767ddf668bf9ace78e76aa4592eb299a09c1f5f9049e089f610d1ba11 scripts/tests/test_sync.py
|
|
36
36
|
2c39ce509e7293e95ffa81f766f46792c76a0497f491806d797e9ec76a822d52 scripts/tests/test_transcripts.py
|
|
37
|
-
|
|
37
|
+
f0d9bd9b031876277addd413b05efb5ec762932254b24975e2bc16b93562dbc7 scripts/tests/test_wins.py
|
|
38
38
|
e18dd87adc7439dbc3485328eb39bdf8c72231713412a7d34f095007e939576f scripts/tests/fixtures/make_report_fixtures.py
|
|
39
39
|
f89b3a779a195670f381c40ec2fcda56b049e14c3cefa0483d583fc11209ab06 scripts/tests/fixtures/prices_sample.json
|
|
40
40
|
2763df8beaebc33e037f0c618b3e1d0e95747f277fa41f139cd5630cf9901226 scripts/tests/fixtures/report-1day.json
|
|
@@ -223,6 +223,7 @@ Each of these needs Alex's explicit go; the report shows the honest fallback unt
|
|
|
223
223
|
- **OpenRouter key (needs Alex)** — a regular inference `OPENROUTER_API_KEY` supplied as an environment variable through the house's secure secret flow; never a management or provisioning key, never read from a settings file, never printed or written to any output. The per-key endpoint gives UTC day/week/month snapshots, so the figure is shown with its bucket label and switches the hero to MEASURED only when the bucket fully covers the report window. A range-by-day source (`/api/v1/activity`) exists but needs a management key, so it is not implemented (G5).
|
|
224
224
|
- **Mac transcripts (needs Alex)** — sessions from Alex's Mac have no transcript on the box, so their cost is EXTRAPOLATED (low confidence) until a device export is merged. Either Alex runs, on the Mac, from a plain checkout of this skill (system `python3` 3.9+): `python3 scripts/acr.py collect --export-device mac --start YYYY-MM-DD --end YYYY-MM-DD --out ~/acr-export` and shares `~/acr-export/device-usage-mac.json` (ids, timestamps, token counts, model names; no prompt or observation text, no settings, no paths); or, only after Alex's explicit go for that specific run (window, machine, destination named), an orchestrating agent runs the same command on the registered Mac through the house's registered-machine tooling and copies only `device-usage-mac.json` to the box. A go for one run is not a go for the next (G6 covered Sep 18–26 only). Merge with `--device-usage`; joined sessions flip to `estimated_usage` with `device: mac`.
|
|
225
225
|
- **Grok Bot (ALWAYS unavailable, no seat count)** — Grok Bot is Cursor's cloud agent. Cursor shows its weekly usage only on the plan screen; there is no API or export, and the house has no xAI key for it. The report says exactly "Grok Bot usage: unavailable" (G4): no dollars, no guessed per-seat price, no seat count. A figure Alex supplies by hand would be entered as `measured_manual` with `entered_by: Alex` and the date, never inferred; that would be a new decision.
|
|
226
|
+
- **Wins from GitHub (read-only)** — besides box transcripts and ship observations, `rollup` lists merged PRs with `gh pr list --state merged` for the repos in `--wins-repo` (default `thedotmack/claude-mem`), filtered by merge time inside the PT window and deduplicated by repo and PR number. When `gh` is missing, unauthenticated or rate-limited, that source reads "unavailable" in Details and the report still completes; it is never a zero. `--no-gh` turns every gh call off.
|
|
226
227
|
- **Win cost (unmeasured until R4)** — sessions are not linked to PRs today, so every win reads "unmeasured (session not linked to PR)". Once commits carry a `Claude-Session: <content_session_id>` trailer (G13: trailer only, bare id), PR and publish wins get `ESTIMATED · session-linked` costs. There is no fallback estimate (G11).
|
|
227
228
|
- **Merge to main, npm publish, `/version-bump`** — each needs Alex's separate go. Pushing the work branch and opening the PR are routine.
|
|
228
229
|
|
|
@@ -395,12 +395,16 @@ def details(d, open_):
|
|
|
395
395
|
out = [f'<details id="details" {"open" if open_ else ""}><summary>Details & evidence</summary>']
|
|
396
396
|
# wins (3.8) and mistakes by day, first so fragment links land near the top
|
|
397
397
|
out.append("<h4>Wins</h4>")
|
|
398
|
+
gs = (d["wins"].get("sources") or {}).get("github_merged_prs")
|
|
399
|
+
if gs:
|
|
400
|
+
rep = "; ".join(f'{esc(r)}: {esc(v["status"])}' + (f' ({v["found"]} found)' if v.get("found") is not None else "") for r, v in (gs.get("repos") or {}).items())
|
|
401
|
+
out.append(f'<div class="muted">GitHub merged PRs (read-only gh pr list): {esc(gs["status"])} · {rep}</div>')
|
|
398
402
|
for w in d["wins"].get("items", []):
|
|
399
403
|
cost = "unmeasured (session not linked to PR)" if w.get("usd") is None else f'≈{usd2(w["usd"])} {tag("extra") if w.get("cost_basis") == "extrapolated" else tag("est")} · {esc(w.get("cost_basis") or "")}'
|
|
400
404
|
tok = w.get("tokens"); tok_s = ", ".join(f"{k} {v:,}" for k, v in tok.items()) if tok else "—"
|
|
401
405
|
out.append(f'<div id="win-{esc(w["win_id"])}"><b>{esc(w["win_id"])}</b> · {esc(w["kind"])} · {esc(w["title"])} · {esc(w.get("ts_pt") or "")}'
|
|
402
406
|
+ (f' · <a href="{esc(w["url"])}">{esc(w["url"])}</a>' if w.get("url") else "")
|
|
403
|
-
+ f'<br><span class="muted">cost {cost} · sessions {esc(", ".join(w.get("sessions") or []) or "—")} · tokens {tok_s} · attribution {esc(d["wins"].get("attribution_method") or "none")} · evidence {esc(", ".join(map(str, w.get("evidence_ids") or [])) or "—")}</span></div>')
|
|
407
|
+
+ f'<br><span class="muted">sources {esc(", ".join(w.get("sources") or []))} · cost {cost} · sessions {esc(", ".join(w.get("sessions") or []) or "—")} · tokens {tok_s} · attribution {esc(d["wins"].get("attribution_method") or "none")} · evidence {esc(", ".join(map(str, w.get("evidence_ids") or [])) or "—")}</span></div>')
|
|
404
408
|
if not d["wins"].get("items"): out.append('<div class="muted">No wins found (merged PR, published version, or praise).</div>')
|
|
405
409
|
if d["wins"].get("dropped"): out.append(f'<div class="muted">{len(d["wins"]["dropped"])} candidate(s) dropped: ' + esc("; ".join(str(x.get("reason")) for x in d["wins"]["dropped"][:6])) + "</div>")
|
|
406
410
|
out.append("<h4>Mistakes by day</h4>")
|
|
@@ -55,7 +55,7 @@ def waste_split(li):
|
|
|
55
55
|
li["productive_cost"] = round(c - li["wasted_cost"] - li["recovery_cost"], 2)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
def build(usage, prices, db, scope, window_block, now=None, use_gh=True, gh=wins.gh_pr_view, remote=wins.git_remote, behavior=True, classify=None, rules_dir=None, glob_pattern=None, device_exports=(), precision=None):
|
|
58
|
+
def build(usage, prices, db, scope, window_block, now=None, use_gh=True, gh=wins.gh_pr_view, remote=wins.git_remote, behavior=True, classify=None, rules_dir=None, glob_pattern=None, device_exports=(), precision=None, wins_repos=wins.WINS_REPOS, gh_list=wins.gh_merged_prs):
|
|
59
59
|
now = now or period.now_pt(); pricer = costs.Pricer(prices)
|
|
60
60
|
sess = evidence.load_sessions(db, scope)
|
|
61
61
|
ev, dev_labels, counts, obs_unpriced = evidence.load_evidence(db, scope, sess, pricer.observer_input_rate)
|
|
@@ -175,7 +175,7 @@ def build(usage, prices, db, scope, window_block, now=None, use_gh=True, gh=wins
|
|
|
175
175
|
rows_by_cs = collections.defaultdict(list)
|
|
176
176
|
for r in rows: rows_by_cs[r["session"]].append(r)
|
|
177
177
|
files = sorted({r["file"] for r in rows if r.get("file")})
|
|
178
|
-
wins_block, wins_by_day = wins.build(files, scope.S, scope.E, ship_obs, sessions_by_cs, rows_by_cs, ratio, days, gh=gh, remote=remote, use_gh=use_gh)
|
|
178
|
+
wins_block, wins_by_day = wins.build(files, scope.S, scope.E, ship_obs, sessions_by_cs, rows_by_cs, ratio, days, gh=gh, remote=remote, use_gh=use_gh, repos=tuple(wins_repos), gh_list=gh_list)
|
|
179
179
|
if wins_block["cost_status"] == "session_linked": # spend not tied to any win, only once at least one win cost exists (2.8)
|
|
180
180
|
wins_block["unattributed_usd"] = round(max(spend["total_estimate_usd"] - wins_block["total_attributed_usd"], 0.0), 2)
|
|
181
181
|
# ---- Phase 2B: behavior pass, mistakes line, mistakes timeline (one union set) ----
|
|
@@ -336,7 +336,7 @@ def run_rollup(args):
|
|
|
336
336
|
with open(args.precision) as fh: precision = json.load(fh)
|
|
337
337
|
cl = dict(enabled=True, cap_usd=getattr(args, "classify_budget", None) or mistakes.classify_mod.CAP_USD, model=getattr(args, "classify_model", None)) if getattr(args, "classify", False) else None
|
|
338
338
|
report, evid, review = build(usage, prices, db, scope, window_block, use_gh=not getattr(args, "no_gh", False),
|
|
339
|
-
behavior=not getattr(args, "no_behavior", False), classify=cl, rules_dir=getattr(args, "rules_dir", None), device_exports=exports, precision=precision)
|
|
339
|
+
behavior=not getattr(args, "no_behavior", False), classify=cl, rules_dir=getattr(args, "rules_dir", None), device_exports=exports, precision=precision, wins_repos=tuple(getattr(args, "wins_repo", None) or wins.WINS_REPOS))
|
|
340
340
|
finally:
|
|
341
341
|
db.close()
|
|
342
342
|
mp = getattr(args, "measured", None) or os.path.join(args.out, "measured.json")
|
|
@@ -131,6 +131,55 @@ def gh_pr_view(number, repo):
|
|
|
131
131
|
except Exception: return None
|
|
132
132
|
|
|
133
133
|
|
|
134
|
+
# ---- read-only merged-PR source (GitHub) ----
|
|
135
|
+
WINS_REPOS = ("thedotmack/claude-mem",) # default repos for the merged-PR list; override with `rollup --wins-repo`
|
|
136
|
+
GH_SOURCE = "github_merged_prs"
|
|
137
|
+
GH_LIST_LIMIT = 200
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _gh_status(returncode, stderr):
|
|
141
|
+
"""Map a failed `gh` call to an honest status; never a number."""
|
|
142
|
+
t = (stderr or "").lower()
|
|
143
|
+
if "rate limit" in t or "api rate limit" in t or "secondary rate" in t: return "rate-limited"
|
|
144
|
+
if "auth login" in t or "authentication" in t or "not logged in" in t or "http 401" in t or "bad credentials" in t: return "unauthenticated"
|
|
145
|
+
return f"gh error (exit {returncode})"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def gh_merged_prs(repo, s_ms, e_ms, run=subprocess.run):
|
|
149
|
+
"""Read-only: gh pr list --repo <repo> --state merged --json number,title,mergedAt,url, searched by merge date
|
|
150
|
+
(UTC days covering the PT window) and then filtered by mergedAt inside [s_ms, e_ms). Returns (rows, status)
|
|
151
|
+
with status "ok" or a reason: "gh missing" | "unauthenticated" | "rate-limited" | "gh error (exit n)"."""
|
|
152
|
+
a = dt.datetime.fromtimestamp(s_ms / 1000, dt.timezone.utc).date() - dt.timedelta(days=1)
|
|
153
|
+
b = dt.datetime.fromtimestamp(e_ms / 1000, dt.timezone.utc).date() + dt.timedelta(days=1)
|
|
154
|
+
cmd = ["gh", "pr", "list", "--repo", repo, "--state", "merged", "--limit", str(GH_LIST_LIMIT), "--json", "number,title,mergedAt,url",
|
|
155
|
+
"--search", f"merged:{a.isoformat()}..{b.isoformat()}"]
|
|
156
|
+
try:
|
|
157
|
+
r = run(cmd, capture_output=True, text=True, timeout=40)
|
|
158
|
+
except FileNotFoundError: return [], "gh missing"
|
|
159
|
+
except (subprocess.TimeoutExpired, OSError) as ex: return [], f"gh error ({type(ex).__name__})"
|
|
160
|
+
if r.returncode != 0: return [], _gh_status(r.returncode, r.stderr)
|
|
161
|
+
try: rows = json.loads(r.stdout or "[]")
|
|
162
|
+
except ValueError: return [], "gh error (unparseable output)"
|
|
163
|
+
out = []
|
|
164
|
+
for x in rows:
|
|
165
|
+
if not x.get("mergedAt") or not x.get("number"): continue
|
|
166
|
+
ms = _ts_ms(x["mergedAt"])
|
|
167
|
+
if s_ms <= ms < e_ms: out.append(dict(number=int(x["number"]), title=x.get("title") or "", url=x.get("url"), ts_ms=ms))
|
|
168
|
+
return out, "ok"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def gh_list_candidates(repos, s_ms, e_ms, lister=gh_merged_prs):
|
|
172
|
+
"""Candidates from the merged-PR list per repo plus a per-repo status for the report."""
|
|
173
|
+
cands = []; status = {}
|
|
174
|
+
for repo in repos:
|
|
175
|
+
rows, st = lister(repo, s_ms, e_ms)
|
|
176
|
+
status[repo] = dict(status=st, found=len(rows) if st == "ok" else None)
|
|
177
|
+
for x in rows:
|
|
178
|
+
cands.append(dict(kind="pr", source=GH_SOURCE, repo=repo, number=x["number"], title=x["title"], url=x["url"], ts_ms=x["ts_ms"],
|
|
179
|
+
project=repo.split("/")[-1], sessions=[], evidence_ids=[], gh_listed=True))
|
|
180
|
+
return cands, status
|
|
181
|
+
|
|
182
|
+
|
|
134
183
|
def session_trailers(message):
|
|
135
184
|
"""Bare content_session_ids named by `Claude-Session:` trailers in one commit message (G13).
|
|
136
185
|
Non-bare values (URLs, ids with spaces or parentheses, anything else) are rejected; `Session:`
|
|
@@ -182,7 +231,8 @@ def _key(c, dropped):
|
|
|
182
231
|
dropped.append(dict(source=c["source"], reason="tag push without an identifiable tag", cmd=c.get("cmd"))); return None
|
|
183
232
|
|
|
184
233
|
|
|
185
|
-
def build(files, s_ms, e_ms, ship_obs, sessions_by_cs, rows_by_cs, ratio, days, gh=gh_pr_view, remote=git_remote, use_gh=True
|
|
234
|
+
def build(files, s_ms, e_ms, ship_obs, sessions_by_cs, rows_by_cs, ratio, days, gh=gh_pr_view, remote=git_remote, use_gh=True,
|
|
235
|
+
repos=WINS_REPOS, gh_list=gh_merged_prs):
|
|
186
236
|
"""Returns (wins_block, wins_by_day). sessions_by_cs: cs -> dict(project, memory_session_id, has_transcript,
|
|
187
237
|
observer_tokens, cwd); rows_by_cs: cs -> usage rows with ts_ms and x1e6 (None when unpriced)."""
|
|
188
238
|
repo_cache = {}
|
|
@@ -202,6 +252,10 @@ def build(files, s_ms, e_ms, ship_obs, sessions_by_cs, rows_by_cs, ratio, days,
|
|
|
202
252
|
if c["kind"] == "pr" and not c.get("repo"): c["repo"] = (remote(c["cwd"]) if remote and c.get("cwd") else None) or project_repo(c["project"])
|
|
203
253
|
cands.append(c)
|
|
204
254
|
cands += list(observation_candidates(ship_obs, project_repo))
|
|
255
|
+
gh_status = {}
|
|
256
|
+
if use_gh and gh_list and s_ms is not None and e_ms is not None:
|
|
257
|
+
gh_cands, gh_status = gh_list_candidates(repos, s_ms, e_ms, lister=gh_list); cands += gh_cands
|
|
258
|
+
elif repos: gh_status = {r: dict(status="unavailable (gh disabled for this run)", found=None) for r in repos}
|
|
205
259
|
merged = collections.OrderedDict()
|
|
206
260
|
for c in cands:
|
|
207
261
|
key = _key(c, dropped)
|
|
@@ -210,16 +264,18 @@ def build(files, s_ms, e_ms, ship_obs, sessions_by_cs, rows_by_cs, ratio, days,
|
|
|
210
264
|
if w is None:
|
|
211
265
|
w = merged[key] = dict(win_id=None, kind=c["kind"], key=key, title=c.get("title") or key, url=None, ts_ms=c["ts_ms"],
|
|
212
266
|
project=c.get("project"), repo=c.get("repo"), number=c.get("number"), evidence_ids=[], sessions=[],
|
|
213
|
-
sources=[], confirmed=False, linked_sessions=[])
|
|
267
|
+
sources=[], confirmed=False, linked_sessions=[], gh_listed=False)
|
|
214
268
|
for eid in c.get("evidence_ids", []):
|
|
215
269
|
if eid not in w["evidence_ids"]: w["evidence_ids"].append(eid)
|
|
216
270
|
for sid in c.get("sessions", []):
|
|
217
271
|
if sid and sid not in w["sessions"]: w["sessions"].append(sid)
|
|
218
272
|
if c["source"] not in w["sources"]: w["sources"].append(c["source"])
|
|
219
|
-
if c
|
|
273
|
+
if c.get("gh_listed"): # the registry's own merge time, title and url win over transcript guesses
|
|
274
|
+
w["gh_listed"] = True; w["confirmed"] = True; w["ts_ms"] = c["ts_ms"]; w["url"] = w["url"] or c.get("url"); w["title"] = c.get("title") or w["title"]
|
|
275
|
+
elif c["ts_ms"] and (w["ts_ms"] is None or c["ts_ms"] < w["ts_ms"]): w["ts_ms"] = c["ts_ms"]
|
|
220
276
|
items = []
|
|
221
277
|
for key, w in merged.items():
|
|
222
|
-
if w["kind"] == "pr" and use_gh and gh and w.get("repo") and w.get("number"):
|
|
278
|
+
if w["kind"] == "pr" and use_gh and gh and w.get("repo") and w.get("number") and not (w["gh_listed"] and w["sources"] == [GH_SOURCE]): # listed-only wins need no second call
|
|
223
279
|
info = gh(w["number"], w["repo"])
|
|
224
280
|
if info is not None:
|
|
225
281
|
if not info.get("mergedAt"): dropped.append(dict(key=key, reason="gh pr view: not merged")); continue
|
|
@@ -249,6 +305,10 @@ def build(files, s_ms, e_ms, ship_obs, sessions_by_cs, rows_by_cs, ratio, days,
|
|
|
249
305
|
total = round(sum(w["usd"] for w in items if w["usd"] is not None), 2) if linked else None
|
|
250
306
|
block = dict(items=out, cost_status="session_linked" if linked else "unmeasured", attribution_method=ATTRIBUTION if linked else None,
|
|
251
307
|
unattributed_usd=None, total_attributed_usd=total, praise_n=len(praise), dropped=dropped, gh_unconfirmed=gh_unconfirmed,
|
|
308
|
+
sources=dict(transcripts="box transcripts (gh pr merge / npm publish / release / tag push)", ship_observations="claude-mem ship observations",
|
|
309
|
+
**{GH_SOURCE: dict(label="GitHub merged PRs (read-only gh pr list)", repos={r: v for r, v in gh_status.items()},
|
|
310
|
+
status=("ok" if gh_status and all(v["status"] == "ok" for v in gh_status.values()) else "unavailable" if gh_status else "off"),
|
|
311
|
+
found=sum(v["found"] or 0 for v in gh_status.values()) if gh_status else 0)}),
|
|
252
312
|
note="finished line items (shipped/completed) are outcomes, not wins (G12); praise detection is a Phase 2B hook")
|
|
253
313
|
by_day = [dict(day_pt=d, count=sum(1 for w in out if w["day_pt"] == d),
|
|
254
314
|
usd=(round(sum(w["usd"] for w in out if w["day_pt"] == d and w["usd"] is not None), 2) if linked else None),
|
|
@@ -148,7 +148,8 @@ def build_parser():
|
|
|
148
148
|
r.add_argument("--out", required=True, metavar="DIR", help="directory holding usage.json and prices.json; outputs land here")
|
|
149
149
|
r.add_argument("--prices", metavar="FILE", help="price table to use instead of DIR/prices.json")
|
|
150
150
|
r.add_argument("--db", metavar="FILE", help=argparse.SUPPRESS)
|
|
151
|
-
r.add_argument("--no-gh", action="store_true", help="skip the
|
|
151
|
+
r.add_argument("--no-gh", action="store_true", help="skip every gh call: the merged-PR list source and the per-PR confirmation (wins from GitHub show as unavailable)")
|
|
152
|
+
r.add_argument("--wins-repo", action="append", metavar="OWNER/REPO", help="repo(s) for the read-only merged-PR wins source (default: thedotmack/claude-mem; repeatable)")
|
|
152
153
|
r.add_argument("--no-behavior", action="store_true", help="skip the Phase 2B behavior pass (no mistakes line)")
|
|
153
154
|
r.add_argument("--classify", action="store_true", help="run the optional classifier on unsettled candidates (off by default, G8)")
|
|
154
155
|
r.add_argument("--classify-budget", type=float, metavar="USD", help="hard cap per run for --classify (default $2.00, G8)")
|
|
@@ -102,8 +102,9 @@ class Build(Transcripts):
|
|
|
102
102
|
def ship(self, n=4125, ms=S + 6 * H):
|
|
103
103
|
return [dict(id=9, title=f"PR #{n} merged to main: x", ts_ms=ms, project="claude-mem", memory_session_id="m1", content_session_id="cs1")]
|
|
104
104
|
|
|
105
|
-
def build(self, files=(), ship_obs=(), rows=None, gh=None, ratio=None, remote=lambda cwd: "o/r"):
|
|
106
|
-
return wins.build(list(files), S, E, list(ship_obs), self.sessions(), rows or {}, ratio, DAYS, gh=gh or fake_gh({}), remote=remote,
|
|
105
|
+
def build(self, files=(), ship_obs=(), rows=None, gh=None, ratio=None, remote=lambda cwd: "o/r", gh_list=None, repos=("o/r",)):
|
|
106
|
+
return wins.build(list(files), S, E, list(ship_obs), self.sessions(), rows or {}, ratio, DAYS, gh=gh or fake_gh({}), remote=remote,
|
|
107
|
+
use_gh=(gh is not None or gh_list is not None), repos=repos, gh_list=gh_list)
|
|
107
108
|
|
|
108
109
|
def test_no_link_means_every_usd_null(self):
|
|
109
110
|
f = self.write("a.jsonl", tool_lines("cs1", S + 2 * H, "gh pr merge 5 --repo o/r"))
|
|
@@ -170,6 +171,47 @@ class Build(Transcripts):
|
|
|
170
171
|
block, by_day = self.build([], []) # completed sessions exist in sessions(), but no merge/publish evidence
|
|
171
172
|
self.assertEqual(block["items"], []); self.assertEqual([d["count"] for d in by_day], [0, 0, 0])
|
|
172
173
|
|
|
174
|
+
# ---- read-only GitHub merged-PR source ----
|
|
175
|
+
def test_gh_list_adds_wins_dedupes_and_labels_source(self):
|
|
176
|
+
f = self.write("a.jsonl", tool_lines("cs1", S + 2 * H, "gh pr merge 4125 --repo o/r"))
|
|
177
|
+
lister = lambda repo, s, e: ([dict(number=4125, title="from github", url="https://github.com/o/r/pull/4125", ts_ms=S + 3 * H),
|
|
178
|
+
dict(number=4200, title="only on github", url="https://github.com/o/r/pull/4200", ts_ms=S + 30 * H)], "ok")
|
|
179
|
+
block, by_day = self.build([f], self.ship(4125), gh_list=lister)
|
|
180
|
+
keys = {i["key"]: i for i in block["items"]}
|
|
181
|
+
self.assertEqual(sorted(keys), ["o/r#4125", "o/r#4200"]) # 4125 counted once across three sources
|
|
182
|
+
self.assertEqual(set(keys["o/r#4125"]["sources"]), {"transcript", "ship_observation", wins.GH_SOURCE}); self.assertTrue(keys["o/r#4125"]["confirmed"])
|
|
183
|
+
self.assertEqual((keys["o/r#4200"]["sources"], keys["o/r#4200"]["title"], keys["o/r#4200"]["usd"], keys["o/r#4200"]["cost_status"]), ([wins.GH_SOURCE], "only on github", None, "unmeasured"))
|
|
184
|
+
src = block["sources"][wins.GH_SOURCE]; self.assertEqual((src["status"], src["found"], src["repos"]["o/r"]["status"]), ("ok", 2, "ok"))
|
|
185
|
+
self.assertEqual([d["count"] for d in by_day], [1, 1, 0])
|
|
186
|
+
|
|
187
|
+
def test_gh_list_unavailable_never_zero_and_report_completes(self):
|
|
188
|
+
f = self.write("a.jsonl", tool_lines("cs1", S + 2 * H, "gh pr merge 4125 --repo o/r"))
|
|
189
|
+
for reason in ("gh missing", "unauthenticated", "rate-limited"):
|
|
190
|
+
block, _ = self.build([f], gh_list=lambda repo, s, e, r=reason: ([], r))
|
|
191
|
+
src = block["sources"][wins.GH_SOURCE]
|
|
192
|
+
self.assertEqual((src["status"], src["repos"]["o/r"]["status"], src["repos"]["o/r"]["found"]), ("unavailable", reason, None), reason)
|
|
193
|
+
self.assertEqual(len(block["items"]), 1) # the other sources still ship
|
|
194
|
+
block, _ = self.build([f], gh=fake_gh({}), gh_list=None)
|
|
195
|
+
self.assertEqual(block["sources"][wins.GH_SOURCE]["repos"]["o/r"]["status"], "unavailable (gh disabled for this run)")
|
|
196
|
+
|
|
197
|
+
def test_gh_merged_prs_window_edges_and_failures(self):
|
|
198
|
+
rows = [dict(number=1, title="a", url="u1", mergedAt=iso(S)), dict(number=2, title="b", url="u2", mergedAt=iso(E)), dict(number=3, title="c", url="u3", mergedAt=iso(E - 1000)), dict(number=4, title="no merge", url="u4", mergedAt=None)]
|
|
199
|
+
ok = lambda cmd, **k: type("R", (), dict(returncode=0, stdout=json.dumps(rows), stderr=""))()
|
|
200
|
+
out, st = wins.gh_merged_prs("o/r", S, E, run=ok)
|
|
201
|
+
self.assertEqual((st, [x["number"] for x in out]), ("ok", [1, 3])) # start inclusive, end exclusive, unmerged dropped
|
|
202
|
+
def missing(cmd, **k): raise FileNotFoundError("gh")
|
|
203
|
+
self.assertEqual(wins.gh_merged_prs("o/r", S, E, run=missing), ([], "gh missing"))
|
|
204
|
+
auth = lambda cmd, **k: type("R", (), dict(returncode=4, stdout="", stderr="To get started with GitHub CLI, please run: gh auth login"))()
|
|
205
|
+
self.assertEqual(wins.gh_merged_prs("o/r", S, E, run=auth)[1], "unauthenticated")
|
|
206
|
+
rl = lambda cmd, **k: type("R", (), dict(returncode=1, stdout="", stderr="GraphQL: API rate limit exceeded for user"))()
|
|
207
|
+
self.assertEqual(wins.gh_merged_prs("o/r", S, E, run=rl)[1], "rate-limited")
|
|
208
|
+
junk = lambda cmd, **k: type("R", (), dict(returncode=0, stdout="not json", stderr=""))()
|
|
209
|
+
self.assertEqual(wins.gh_merged_prs("o/r", S, E, run=junk)[1], "gh error (unparseable output)")
|
|
210
|
+
seen = []
|
|
211
|
+
def spy(cmd, **k): seen.append(cmd); return ok(cmd)
|
|
212
|
+
wins.gh_merged_prs("o/r", S, E, run=spy)
|
|
213
|
+
self.assertEqual(seen[0][:6], ["gh", "pr", "list", "--repo", "o/r", "--state"]); self.assertEqual(seen[0][1:3], ["pr", "list"]); self.assertNotIn("merge", seen[0][2]) # read-only: list, never merge
|
|
214
|
+
|
|
173
215
|
def test_publish_from_transcript_and_observation_dedup(self):
|
|
174
216
|
f = self.write("a.jsonl", tool_lines("cs1", S + 2 * H, "npm publish", "npm notice Publishing to https://registry.npmjs.org/\n+ claude-mem@13.25.3"))
|
|
175
217
|
obs = [dict(id=3, title="claude-mem 13.25.3 published to npm", ts_ms=S + 3 * H, project="claude-mem/wt", memory_session_id="m1", content_session_id="cs1")]
|
package/package.json
CHANGED