diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 16211751..86ccabfd 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ "name": "memesh", "source": "./", "description": "MeMesh \u2014 agentic memory for coding agents. Captured from the agent's real work via hooks, recalled when it acts. One SQLite file, zero cloud required.", - "version": "4.10.0", + "version": "4.10.1", "author": { "name": "PCIRCLE AI" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b8b88196..a8d5c38c 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -4,7 +4,7 @@ "author": { "name": "PCIRCLE AI" }, - "version": "4.10.0", + "version": "4.10.1", "mcpServers": "./.claude-plugin/mcp.json", "homepage": "https://github.com/PCIRCLE-AI/memesh", "repository": "https://github.com/PCIRCLE-AI/memesh", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index ace46418..313c48e7 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "memesh", "description": "MeMesh — agentic memory for coding agents.", - "version": "4.10.0", + "version": "4.10.1", "mcpServers": "./.codex-plugin/mcp.json" } diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 06449847..90c20e91 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -91,6 +91,7 @@ jobs: run: npm pack --dry-run - name: Publish to npm - run: npm publish --access public --provenance + run: npm publish --access public --provenance --tag "$MEMESH_NPM_DIST_TAG" env: + MEMESH_NPM_DIST_TAG: ${{ github.event.release.prerelease && 'next' || 'latest' }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3c8e39..38928dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,49 @@ All notable changes to MeMesh are documented here. -## [Unreleased] +## [4.10.1] — 2026-09-14 + +Includes the changes documented under 4.10.0 below; 4.10.0 was not published +to npm. The previous public release is 4.9.4. + +### Added + +- **`qa:live-journey` now carries one v4 receipt for core memory and live-host + delivery.** Every real Codex or Claude run first exercises five shared + journeys: remember/recall, SessionStart briefing injection, quiet commit + capture, Stop insight capture, and a packed upgrade from public baselines. + Each row records its successful path, a representative failure, persisted + effect readback, the exercised boundary, and cleanup. `--core-only` runs the + non-interactive catalogue without host credentials; release receipts still + require the separate real Codex and Claude model-visible paths. ### Fixed +- Settings that cannot be read show an unknown policy and reload guidance + instead of appearing to have automatic updates turned off. +- **Update-check failures explain how to retry.** Dashboard Settings keeps + technical errors collapsed and shows connection and npm registry guidance. + Doctor no longer treats a failed first check as unattempted or an unpublished + local version as npm's latest release. +- The optional Claude Channel registration reminder now uses the selected + dashboard language, including its setup instructions. +- **An observation removed with `forget` stays removed from later Stop snapshots + (#346).** Files, fixes, and summary snapshots exclude the exact removed text + while continuing to accept new content. Explicit `remember` can restore it. + Stop replay tests and a memory invariant detect resurrection. See + `docs/postmortems/2026-09-14-observation-forget.md`. +- **Quiet and redirected Git commits are captured from repository state + (#321).** The PostToolUse hook keeps the existing Git-output parser as a + optional output signal, but commit-producing commands now reconcile the repository's + current `HEAD` with a private marker keyed by the shared Git common + directory. This covers `git commit -q`, redirected output, merge, + cherry-pick, revert, and linked worktrees. A first observation records a + baseline without importing old history; later ranges capture at most the + newest 20 commits, tag multi-commit ranges `origin:batch`, and record how + many older commits were skipped. Failed commands and unresolvable or + unchanged heads leave explicit hook outcome records instead of silently + looking like a broken hook. See + `docs/postmortems/2026-09-14-quiet-commit-capture.md`. - **The isolated release suite now owns its npm cache.** The release runner no longer inherits a maintainer's `~/.npm` cache when nested tests execute `npm pack`, so local ownership damage cannot turn an otherwise isolated diff --git a/CODEMAP.md b/CODEMAP.md index 051897b9..0e464914 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -1,6 +1,6 @@ # CODEMAP -**Version**: 4.10.0 +**Version**: 4.10.1 A navigation map for the codebase: *"I want to change X — which file?"* For the design rationale behind these modules see [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e44ce18e..15fe96e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,11 +49,28 @@ A release is one operation, not three commands: git checkout main && git pull # the release PR is already merged npm run qa:pre-release # the enumerable gates, in one door # Produce fresh .qa/codex-report.json and .qa/claude-report.json receipts. -npm run release:finish -- --dry-run # what it would do, or every reason it refuses -npm run release:finish -npm run qa:post-release # after the publish workflow is green +npm run release:finish -- --prerelease --dry-run +# After owner authorization, publish the trial through the existing workflow: +npm run release:finish -- --prerelease +npm run qa:post-release -- --dist-tag next ``` +The prerelease publishes the exact version to npm `next` and verifies that +`latest` stays unchanged. Explicit `@next` or version installs opt into the trial; +ordinary npm installs continue using `latest`. The plugin marketplace follows +`main`, so this npm tag separation does not isolate plugin users after the merge. + +Retain green post-release and core/live-host journey evidence for the same +candidate, then observe at least one working day with the capture-liveness +check passing. After owner authorization to promote, move the existing npm +artifact with `npm dist-tag add @pcircle/memesh@ latest`, clear the +GitHub prerelease flag with `gh release edit v --prerelease=false`, +read back both states, and rerun `npm run qa:post-release` in its default +`latest` mode. Promotion does not publish the package again. + +Without `--prerelease`, `release:finish` retains the stable `latest` behavior; +do not use that mode to skip trial observation for a new candidate. + `npm run qa:pre-release` runs the build, requires the independent UI review below against that build, then runs the `verify:artifact` sequence and `audit:memory`, prints each step's real exit code, and ends with the list of checks it could not run — the interactive live journey among them. It is not a @@ -128,7 +145,7 @@ binding. It cannot judge language quality, authenticate authorship, or prove that observations are true. The release owner must inspect the evidence and have the independent reviewer replay the claims. It grants no release authority. -`release:finish` requires separate v3 live receipts for both hosts on the exact +`release:finish` requires separate v4 live receipts for both hosts on the exact clean commit, produced within the previous 24 hours. The Claude receipt comes from `npm run qa:live-journey -- --host claude --out .qa/claude-report.json`. The Codex receipt must come from an installed-plugin @@ -141,7 +158,8 @@ lifecycle; the narrower account-free harness mode cannot satisfy that gate. See [`docs/platforms/agent-messaging.md`](docs/platforms/agent-messaging.md#repeatable-owner-run-live-checks). `npm run qa:post-release` is the half a fresh-clone gate cannot do: it asks the -registry whether the version is really published and really `latest`, installs +registry whether the version is published at the selected tag (`latest` by +default, or `next` during the trial), installs it from the registry into a throwaway prefix and runs it, runs the shipped capture hooks against a throwaway graph (the `capture` receipt: post-commit must store one commit and session-summary one session insight), and then asks whether diff --git a/dashboard/dist/index.html b/dashboard/dist/index.html index a488b948..da6d809a 100644 --- a/dashboard/dist/index.html +++ b/dashboard/dist/index.html @@ -10,7 +10,7 @@ Newsreader = memory content (italic carries the inferred-vs-verbatim distinction), Geist Mono = data. Offline, the stacks fall back. --> - +`)}`,y=`https://github.com/PCIRCLE-AI/memesh/issues/new?title=${encodeURIComponent("[Bug] memesh doctor reported issues")}&body=${encodeURIComponent(k)}&labels=${encodeURIComponent("feedback,from-dashboard,bug,doctor-warning")}`;Wo(y)||(r(y),d(!1))}async function T(C){if(!(!C.fixId||m)){c(C.id),u(null),f(null);try{const k=await I("POST","/v1/doctor/fix",{id:C.id});u(C.id),k?.restartRequired&&f(n("doctorBanner.restartRequired")),window.dispatchEvent(new Event("memesh:data-changed"))}catch(k){f(k instanceof Error?k.message:String(k))}finally{c(null)}}}async function R(){if(a)try{await navigator.clipboard.writeText(a),d(!0)}catch{d(!1)}}const L=t.status==="FAIL",A=L?"var(--danger)":"var(--warning)",p=L?"var(--danger-soft)":"var(--warning-soft)";return e("div",{role:"alert",style:{position:"relative",margin:"12px auto 0",maxWidth:920,padding:"12px 16px",border:`1px solid ${A}`,borderRadius:"var(--radius)",background:p,color:"var(--text-1)"},children:[e("button",{type:"button",onClick:P,"aria-label":n("doctorBanner.dismiss"),style:{position:"absolute",top:6,right:8,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4,minWidth:24,minHeight:24},children:"×"}),e("div",{style:{fontSize:14,fontWeight:600,color:A,marginBottom:6},children:n(L?"doctorBanner.failTitle":"doctorBanner.warnTitleSoft")}),e("ul",{style:{margin:"6px 0 10px",paddingLeft:18,fontSize:14,lineHeight:1.5,color:"var(--text-2)"},children:[E.slice(0,3).map(C=>{const k=ua(C),D=C.fixId==="config-retired-settings"||C.fixId==="plugin-cache-refresh";return e("li",{children:[e("strong",{children:[ha(C),":"]})," ",ma(C),k&&e(q,{children:[!D&&e(q,{children:[" — ",e("em",{style:{color:"var(--text-3)"},children:k}),ra(k).map(y=>e(je,{id:"doctor-fix-command",command:y},y))]}),D&&e("div",{style:{marginTop:7},children:e("button",{type:"button",class:"btn btn-sm",disabled:m!==null,onClick:()=>{T(C)},children:m===C.id?n("doctorBanner.fixInProgress"):h===C.id?n("doctorBanner.fixDone"):n("doctorBanner.fix")})})]})]},C.id)}),E.length>3&&e("li",{style:{color:"var(--text-3)"},children:n("doctorBanner.moreCount",{n:E.length-3})})]}),g&&e("div",{role:"status",style:{margin:"4px 0 8px",color:"var(--text-2)",fontSize:14},children:g}),L&&e("div",{style:{display:"flex",gap:8,alignItems:"center",flexWrap:"wrap"},children:[e("button",{type:"button",class:"btn",onClick:_,style:{fontSize:14,padding:"4px 12px"},children:n("doctorBanner.getHelp")}),e(Go,{id:"doctor-help"}),e("span",{style:{fontSize:14,color:"var(--text-3)"},children:n("doctorBanner.helpHint")})]}),a&&e("div",{role:"alert",style:{marginTop:8,fontSize:14},children:[e("div",{children:n("feedback.popupBlocked")}),e("a",{href:a,target:"_blank",rel:"noopener noreferrer",children:n("feedback.retry")})," ",e("button",{type:"button",class:"btn btn-sm",onClick:()=>{R()},children:n(l?"feedback.linkCopied":"feedback.copyLink")})]})]})}const yo="memesh.insightsBanner.dismissed";function ya({currentTab:t,onNavigateToInsights:o}){const[s,i]=b(0),[a,r]=b(()=>{try{return sessionStorage.getItem(yo)==="true"}catch{return!1}});if(W(()=>{let m=!0;const c=()=>{I("GET","/v1/dream/proposals?status=pending").then(u=>{if(!m)return;const g=Array.isArray(u)?u:u?.data??[];i(g.length)}).catch(()=>{})};c();const h=()=>c();return window.addEventListener("memesh:data-changed",h),()=>{m=!1,window.removeEventListener("memesh:data-changed",h)}},[]),t==="Home"||a||s===0)return null;function l(m){m.stopPropagation(),r(!0);try{sessionStorage.setItem(yo,"true")}catch{}}const d=n("banner.pendingInsights",{n:s,s:s===1?"":"s"});return e("div",{role:"button","aria-label":n("banner.viewAll"),onClick:o,onKeyDown:m=>{(m.key==="Enter"||m.key===" ")&&(m.preventDefault(),o())},tabIndex:0,style:{position:"relative",margin:"12px auto 0",maxWidth:920,padding:"10px 40px 10px 16px",border:"1px solid rgba(143, 242, 92, 0.32)",borderRadius:"var(--radius)",background:"var(--life-soft)",color:"var(--text-1)",cursor:"pointer",fontSize:14,lineHeight:1.5,display:"flex",alignItems:"center",justifyContent:"space-between",gap:8},children:[e("span",{style:{flex:1},children:[e("span",{style:{marginRight:6},"aria-hidden":"true",children:"💡"}),d]}),e("button",{type:"button",onClick:l,"aria-label":n("banner.dismiss"),style:{position:"absolute",top:6,right:8,background:"transparent",border:"none",color:"var(--text-3)",fontSize:18,lineHeight:1,cursor:"pointer",padding:4,minWidth:24,minHeight:24},children:"×"})]})}const Vo=["Home","Memories","Project","Settings"],va={Home:"tab.home",Memories:"tab.memories",Project:"tab.project",Settings:"tab.settings"},ba={Insights:"Home",Analytics:"Home",Search:"Memories",Browse:"Memories",Manage:"Memories",Lessons:"Memories",Graph:"Project"},$o="memesh.tab";function ka(){const t=o=>o?Vo.includes(o)?o:ba[o]??null:null;try{const o=new URLSearchParams(window.location.search),s=t(o.get("tab"));if(s)return s;const i=t(localStorage.getItem($o));if(i)return i}catch{}return"Home"}function xa(){const[t,o]=b(()=>us()),[s,i]=b(ka),a=le(A=>{i(A)},[]),[r,l]=b(()=>new Set);W(()=>{l(A=>A.has(s)?A:new Set(A).add(s))},[s]);const d=A=>r.has(A)||s===A;W(()=>{try{localStorage.setItem($o,s)}catch{}try{const A=new URL(window.location.href);A.searchParams.set("tab",s),history.replaceState(null,"",A)}catch{}},[s]);const[m,c]=b(null),[h,u]=b(0),g=X(0),[f,E]=b(""),[S,P]=b(!1);W(()=>{const A=()=>P(!0);return window.addEventListener("memesh:auth-required",A),()=>window.removeEventListener("memesh:auth-required",A)},[]);const[_,T]=b(!1),R=le(()=>{const A=++g.current;I("GET","/v1/health").then(p=>{A===g.current&&(c(p),E(""),P(!1))}).catch(p=>{if(A===g.current){if(p instanceof Et){T(bt()!==null),P(!0),E("");return}E(p.message)}})},[]);W(()=>{R();const A=()=>{u(p=>p+1),R()};return window.addEventListener("memesh:data-changed",A),()=>window.removeEventListener("memesh:data-changed",A)},[R]);const L=Vo.map(A=>({key:A,label:n(va[A])}));return S?e(da,{currentToken:bt(),rejected:_,onSubmit:A=>{ks(A),T(!1),P(!1),R()}}):e("div",{class:"shell",children:[e(ys,{health:m,error:f}),m&&f&&e("div",{class:"error-box",role:"alert",style:{margin:"8px 16px 0"},children:[n("common.error"),": ",f]}),(m?.demo_entity_count??0)>0&&e(go,{health:m}),e("div",{class:"notice-slot",children:[e(fa,{}),(m?.demo_entity_count??0)===0&&e(go,{health:m}),e(ya,{currentTab:s,onNavigateToInsights:()=>a("Home")})]}),e(vs,{tabs:L,active:s,onSelect:A=>a(A)}),e("div",{class:"main",children:[e("div",{id:"panel-Home",role:"tabpanel","aria-labelledby":"tab-Home",class:`panel ${s==="Home"?"active":""}`,children:s==="Home"&&e(mn,{health:m,dataRevision:h,onNavigate:a})}),e("div",{id:"panel-Memories",role:"tabpanel","aria-labelledby":"tab-Memories",class:`panel ${s==="Memories"?"active":""}`,children:d("Memories")&&e(jn,{health:m,dataRevision:h})}),e("div",{id:"panel-Project",role:"tabpanel","aria-labelledby":"tab-Project",class:`panel ${s==="Project"?"active":""}`,children:d("Project")&&e(ta,{health:m,dataRevision:h})}),e("div",{id:"panel-Settings",role:"tabpanel","aria-labelledby":"tab-Settings",class:`panel ${s==="Settings"?"active":""}`,children:s==="Settings"&&e(ia,{locale:t,onLocaleChange:o})})]}),e(la,{health:m}),e("footer",{class:"app-footer",children:n("brand.subtitle")})]})}as(e(xa,{}),document.getElementById("app")); diff --git a/dashboard/src/components/SettingsTab.tsx b/dashboard/src/components/SettingsTab.tsx index ac96aa37..8cf2cfad 100644 --- a/dashboard/src/components/SettingsTab.tsx +++ b/dashboard/src/components/SettingsTab.tsx @@ -209,7 +209,7 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) { ? t('settings.updateDeprecatedNoTarget') : t('settings.updateDeprecatedTargetUnknown') : updateStatus.freshness === 'unavailable' - ? t('settings.updateNoSuccessfulChecks') + ? t(updateStatus.lastError ? 'settings.updateUnavailable' : 'settings.updateNoSuccessfulChecks') : !updateStatus.checkSucceeded && updateStatus.freshness === 'stale' ? t('settings.updateStale') : !updateStatus.checkSucceeded && updateStatus.freshness === 'cached' @@ -247,6 +247,7 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) { const lastSuccessfulLabel = isCheckingUpdates ? t('common.loading') : formatTimestamp(locale, updateStatus?.lastSuccessfulCheckAt || null); const showLastSuccessful = Boolean(updateStatus?.lastSuccessfulCheckAt); const showLastError = Boolean(updateStatus?.lastError) && !isCheckingUpdates; + const configLoadFailed = !configLoading && !config; return (
@@ -290,7 +291,7 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) { > {t('settings.updatePartialTitle')}
- {t('settings.updatePartialDescription', { message: updateStatus.lastError ?? '' })} + {t('settings.updatePartialDescription')}
)} @@ -339,7 +340,13 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) { )} {showLastError && (
- {t('settings.updateLastError', { message: updateStatus?.lastError || '' })} +
{t('settings.updateRetryGuidance')}
+
+ {t('settings.technicalDetails')} +
+ {t('settings.updateLastError', { message: updateStatus?.lastError || '' })} +
+
)} {updateStatus?.updateAvailable && updateStatus.recommendedCommand && ( @@ -365,11 +372,12 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) {