Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 2 additions & 1 deletion .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
41 changes: 40 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion CODEMAP.md
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
28 changes: 23 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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@<version> latest`, clear the
GitHub prerelease flag with `gh release edit v<version> --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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions dashboard/dist/index.html

Large diffs are not rendered by default.

22 changes: 15 additions & 7 deletions dashboard/src/components/SettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
<div style={{ display: 'grid', gap: 12 }}>
Expand Down Expand Up @@ -290,7 +291,7 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) {
>
<strong style={{ color: 'var(--warning)' }}>{t('settings.updatePartialTitle')}</strong>
<div style={{ marginTop: 4, opacity: 0.9 }}>
{t('settings.updatePartialDescription', { message: updateStatus.lastError ?? '' })}
{t('settings.updatePartialDescription')}
</div>
</div>
)}
Expand Down Expand Up @@ -339,7 +340,13 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) {
)}
{showLastError && (
<div style={{ color: 'var(--warning)', fontSize: 14, lineHeight: 1.5 }}>
{t('settings.updateLastError', { message: updateStatus?.lastError || '' })}
<div role="alert">{t('settings.updateRetryGuidance')}</div>
<details style={{ marginTop: 6 }}>
<summary style={{ cursor: 'pointer' }}>{t('settings.technicalDetails')}</summary>
<div style={{ overflowWrap: 'anywhere', marginTop: 6 }}>
{t('settings.updateLastError', { message: updateStatus?.lastError || '' })}
</div>
</details>
</div>
)}
{updateStatus?.updateAvailable && updateStatus.recommendedCommand && (
Expand All @@ -365,11 +372,12 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) {
</label>
<select
aria-labelledby="settings-autoupdate-label"
value={config?.config.autoUpdate ?? 'off'}
value={config ? (config.config.autoUpdate ?? 'off') : ''}
disabled={!config || configSaving}
onChange={(e) => { void saveAutoUpdate((e.target as HTMLSelectElement).value as AutoUpdatePolicy); }}
style={{ fontSize: 16, padding: '6px 10px', borderRadius: 'var(--radius-sm)', border: '1px solid var(--border)', background: 'var(--bg-input)', color: 'var(--text-1)', cursor: 'pointer' }}
>
{!config && <option value="" disabled>{t(configLoading ? 'common.loading' : 'common.unknown')}</option>}
<option value="off">{t('settings.autoUpdateOff')}</option>
<option value="patch">{t('settings.autoUpdatePatch')}</option>
<option value="minor">{t('settings.autoUpdateMinor')}</option>
Expand All @@ -378,9 +386,9 @@ export function SettingsTab({ locale, onLocaleChange }: SettingsTabProps) {
<div style={{ fontSize: 14, color: 'var(--text-3)', marginTop: 4 }}>
{t('settings.autoUpdateHint')}
</div>
{configMessage && (
<div role={configMessage.startsWith(t('common.error')) ? 'alert' : 'status'} style={{ marginTop: 8, fontSize: 14 }}>
{configMessage}
{(configLoadFailed || configMessage) && (
<div role={configLoadFailed || configMessage.startsWith(t('common.error')) ? 'alert' : 'status'} style={{ marginTop: 8, fontSize: 14 }}>
{configLoadFailed ? t('common.error') + ': ' + t('settings.configLoadFailed') : configMessage}
</div>
)}
</div>
Expand Down
Loading
Loading