Skip to content

Commit 5b0083e

Browse files
authored
Merge pull request #4 from sdwolf4103/feat/memory-quality-cleanup
Release v1.4.0 memory quality cleanup
2 parents a1b9bf4 + 60b9ca7 commit 5b0083e

22 files changed

Lines changed: 1850 additions & 176 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,7 @@ pnpm-lock.yaml
5151

5252
# Superpowers local planning artifacts
5353
docs/superpowers/plans/
54+
55+
# Local dev/admin script inputs
56+
scripts/dev/run-migration-roots.local.txt
57+
scripts/dev/dry-run-roots.local.txt

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.4.0] - 2026-04-28
9+
10+
### Added
11+
12+
- Local migration audit log for the `2026-04-28-quality-cleanup` migration:
13+
`~/.local/share/opencode-working-memory/migration-logs/2026-04-28-quality-cleanup.jsonl`.
14+
- Local extraction rejection log for rejected compaction memory candidates:
15+
`~/.local/share/opencode-working-memory/extraction-rejections.jsonl`.
16+
- Sanitized real-workspace regression fixtures for memory cleanup migration behavior.
17+
- Safe workspace residue cleanup tooling that dry-runs by default and quarantines definite temp/test workspace stores instead of deleting them.
18+
19+
### Changed
20+
21+
- Unified memory quality rules in a shared quality gate for compaction memory candidates and cleanup checks.
22+
- Rewritten compaction memory prompt to reduce over-production of low-quality memories.
23+
- Changed quality cleanup migration to be conservative: it supersedes only high-confidence garbage patterns, including progress snapshots, raw errors, commit/CI snapshots, temporary status notes, active file snapshots, code/API signatures, path-heavy entries, and empty entries.
24+
- Soft heuristic failures (`bad_feedback`, `bad_decision`) are intentionally excluded from automatic migration cleanup to protect durable declarative memories such as branding rules, API facts, release rules, user workflow preferences, and architecture decisions.
25+
- Isolated test runs under a temporary `XDG_DATA_HOME` so test workspaces no longer pollute real local workspace memory data.
26+
27+
### Recovery note
28+
29+
The cleanup migration changes matching entries to `status: "superseded"`; it does not delete the entry. If a useful memory is superseded, inspect the migration audit log and restore by changing that entry back to `status: "active"` in the workspace's `workspace-memory.json`. The migration runs once per workspace.
30+
831
## [1.3.3] - 2026-04-28
932

1033
### Fixed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,17 @@ It includes guards for:
174174

175175
The goal is to remember durable facts, not every detail.
176176

177+
Historical cleanup is intentionally conservative: extraction-time filtering may reject more aggressively, but one-time migration cleanup only supersedes high-confidence garbage patterns. This protects existing durable memories written in declarative style, such as "API endpoint is X" or "Product branding is Y".
178+
179+
For local development cleanup, use:
180+
181+
```bash
182+
npm run cleanup:workspaces -- --dry-run
183+
npm run cleanup:workspaces -- --quarantine
184+
```
185+
186+
The cleanup command only quarantines definite temp/test workspace residues by default. It does not delete unknown missing-root workspaces.
187+
177188
## Configuration
178189

179190
OpenCode Working Memory works out of the box.
@@ -210,6 +221,7 @@ cd opencode-working-memory
210221
npm install
211222
npm test
212223
npm run typecheck
224+
npm run cleanup:workspaces -- --dry-run
213225
```
214226

215227
## Requirements

RELEASE_NOTES.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,105 @@
11
# Release Notes
22

3+
## 1.4.0 (2026-04-28)
4+
5+
### Memory Quality Cleanup
6+
7+
This release improves automatic workspace memory quality without risking broad cleanup of useful existing memories.
8+
9+
The quality gate is now shared across compaction extraction and migration checks, the compaction prompt is stricter about what should become durable memory, and the one-time migration is intentionally conservative.
10+
11+
### What Changed
12+
13+
- **Unified quality rules**: memory quality checks now live in one shared module and apply consistently across feedback, decisions, project facts, and references.
14+
- **Stricter compaction output**: the compaction prompt now tells the model to save fewer memories and prefer durable facts, user preferences, architecture decisions, and hard-to-rediscover references.
15+
- **Conservative migration cleanup**: the `2026-04-28-quality-cleanup` migration only supersedes high-confidence garbage patterns, not every rejected memory.
16+
- **Audit logs**: automatic migration cleanup writes local JSONL audit records so superseded entries can be inspected and restored.
17+
- **Extraction rejection logs**: newly rejected compaction candidates are logged locally to help calibrate future quality rules.
18+
- **Regression coverage**: migration behavior is tested against sanitized real-workspace patterns to prevent mass false positives from coming back.
19+
- **Workspace cleanup tooling**: a dev/admin cleanup command can dry-run or quarantine definite temp/test workspace residues without deleting unknown missing-root workspaces.
20+
- **Test storage isolation**: test runs now use a temporary `XDG_DATA_HOME`, preventing fixture workspaces from polluting real local memory data.
21+
22+
### What Gets Cleaned Up
23+
24+
The migration may supersede existing `source: "compaction"` memories only when they match hard garbage patterns:
25+
26+
- Empty entries
27+
- Progress snapshots, such as "Wave 1 completed successfully"
28+
- Test or suite count snapshots, such as "180 tests passed"
29+
- Raw errors and stack traces
30+
- Commit or CI snapshots
31+
- Temporary status notes, such as "Currently running npm test"
32+
- Active file snapshots
33+
- Code or API signatures
34+
- Path-heavy entries that are just rediscoverable file lists
35+
36+
### What Is Protected
37+
38+
The migration does not supersede entries whose only issue is a soft heuristic failure, such as:
39+
40+
- `bad_feedback`
41+
- `bad_decision`
42+
43+
This protects useful declarative memories like:
44+
45+
- Product branding rules
46+
- API facts
47+
- Release rules
48+
- Architecture decisions
49+
- User workflow preferences
50+
51+
Explicit and manual memories are also protected.
52+
53+
### Migration Behavior
54+
55+
- Runs once per workspace.
56+
- Only affects active `source: "compaction"` entries.
57+
- Marks matching entries as `status: "superseded"` instead of deleting them.
58+
- Adds `quality_cleanup` and `quality:<reason>` tags to superseded entries.
59+
- Writes audit logs to:
60+
`~/.local/share/opencode-working-memory/migration-logs/2026-04-28-quality-cleanup.jsonl`
61+
- Writes extraction rejection logs to:
62+
`~/.local/share/opencode-working-memory/extraction-rejections.jsonl`
63+
64+
### Recovery
65+
66+
If a useful memory is superseded, inspect the migration audit log and restore the entry by changing its status back to `"active"` in the workspace's `workspace-memory.json`.
67+
68+
### Workspace Residue Cleanup
69+
70+
If old test/temp workspace stores already exist locally, inspect them first:
71+
72+
```bash
73+
npm run cleanup:workspaces -- --dry-run
74+
```
75+
76+
To move definite temp/test residues into a local quarantine folder instead of deleting them:
77+
78+
```bash
79+
npm run cleanup:workspaces -- --quarantine
80+
```
81+
82+
The cleanup command skips existing workspace roots and unknown missing-root workspaces by default.
83+
84+
### Upgrade Notes
85+
86+
- No configuration changes required.
87+
- Existing workspace memory files remain compatible.
88+
- The OpenCode config entry stays the same:
89+
90+
```json
91+
{
92+
"plugin": ["opencode-working-memory"]
93+
}
94+
```
95+
96+
### Validation
97+
98+
- `npm test`
99+
- `npm run typecheck`
100+
101+
---
102+
3103
## 1.3.2 (2026-04-27)
4104

5105
### CI Compatibility Patch

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-working-memory",
3-
"version": "1.3.3",
3+
"version": "1.4.0",
44
"description": "Three-layer memory architecture for OpenCode with workspace memory and hot session state",
55
"type": "module",
66
"main": "index.ts",
@@ -16,7 +16,8 @@
1616
"scripts": {
1717
"build": "node -e \"console.log('No build step required: OpenCode loads index.ts directly')\"",
1818
"typecheck": "tsc --noEmit",
19-
"test": "node --test --experimental-strip-types tests/*.test.ts",
19+
"test": "node --import ./tests/setup-xdg-data-home.ts --test --experimental-strip-types tests/*.test.ts",
20+
"cleanup:workspaces": "node --experimental-strip-types scripts/dev/cleanup-workspaces.ts",
2021
"check:compat": "npm install --no-save @opencode-ai/plugin@latest && npm run typecheck && npm test"
2122
},
2223
"keywords": [

scripts/dev/cleanup-workspaces.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Safely inspect or quarantine stale test/temp workspace memory stores.
4+
*
5+
* Default mode is dry-run. Quarantine moves only definite temp/test residues.
6+
* Unknown missing roots are reported but skipped unless --include-orphans is set.
7+
*/
8+
9+
import { cleanupWorkspaceResidues } from "../../src/workspace-cleanup.ts";
10+
11+
type CliOptions = {
12+
mode: "dry-run" | "quarantine";
13+
dataHome?: string;
14+
olderThanDays?: number;
15+
includeOrphans: boolean;
16+
};
17+
18+
function usage(): string {
19+
return `Usage:
20+
npm run cleanup:workspaces -- --dry-run
21+
npm run cleanup:workspaces -- --quarantine
22+
npm run cleanup:workspaces -- --quarantine --older-than-days 1
23+
24+
Options:
25+
--dry-run List candidates without moving anything (default)
26+
--quarantine Move definite temp/test residues to quarantine
27+
--data-home <path> Override XDG data home for testing/admin work
28+
--older-than-days <n> Only consider workspace dirs older than n days
29+
--include-orphans Also quarantine missing non-temp roots (off by default)
30+
--help Show this help
31+
`;
32+
}
33+
34+
function parseArgs(argv: string[]): CliOptions {
35+
const options: CliOptions = { mode: "dry-run", includeOrphans: false };
36+
37+
for (let i = 0; i < argv.length; i++) {
38+
const arg = argv[i];
39+
switch (arg) {
40+
case "--dry-run":
41+
options.mode = "dry-run";
42+
break;
43+
case "--quarantine":
44+
options.mode = "quarantine";
45+
break;
46+
case "--data-home":
47+
options.dataHome = argv[++i];
48+
if (!options.dataHome) throw new Error("--data-home requires a path");
49+
break;
50+
case "--older-than-days": {
51+
const value = Number(argv[++i]);
52+
if (!Number.isFinite(value) || value < 0) throw new Error("--older-than-days requires a non-negative number");
53+
options.olderThanDays = value;
54+
break;
55+
}
56+
case "--include-orphans":
57+
options.includeOrphans = true;
58+
break;
59+
case "--help":
60+
case "-h":
61+
console.log(usage());
62+
process.exit(0);
63+
default:
64+
throw new Error(`Unknown option: ${arg}\n${usage()}`);
65+
}
66+
}
67+
68+
return options;
69+
}
70+
71+
const options = parseArgs(process.argv.slice(2));
72+
const result = await cleanupWorkspaceResidues({
73+
dataHome: options.dataHome,
74+
mode: options.mode,
75+
includeOrphans: options.includeOrphans,
76+
minAgeMs: options.olderThanDays === undefined ? undefined : options.olderThanDays * 24 * 60 * 60 * 1_000,
77+
});
78+
79+
console.log(`Mode: ${result.mode}`);
80+
console.log(`Scanned: ${result.results.length}`);
81+
console.log(`Candidates: ${result.candidates.length}`);
82+
83+
if (result.candidates.length > 0) {
84+
console.log("\nCandidates:");
85+
for (const candidate of result.candidates) {
86+
console.log(`- ${candidate.workspaceKey} ${candidate.classification} root=${candidate.root ?? "<missing>"}`);
87+
console.log(` reasons=${candidate.reasons.join(",")}`);
88+
}
89+
}
90+
91+
if (result.quarantined.length > 0) {
92+
console.log(`\nQuarantined: ${result.quarantined.length}`);
93+
console.log(`Quarantine dir: ${result.quarantineDir}`);
94+
}
95+
96+
const unknownOrphans = result.results.filter(item => item.classification === "orphan_unknown");
97+
if (unknownOrphans.length > 0 && !options.includeOrphans) {
98+
console.log(`\nUnknown missing-root workspaces skipped: ${unknownOrphans.length}`);
99+
console.log("Use --include-orphans only after manually confirming they are safe to quarantine.");
100+
}

scripts/dev/run-migration.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Local helper to trigger migration on workspace roots.
3+
*
4+
* Usage:
5+
* MIGRATION_RUN_ROOTS=/path/a:/path/b bun run scripts/dev/run-migration.ts
6+
*
7+
* Or create a local file (gitignored):
8+
* echo "/path/to/workspace1" > scripts/dev/run-migration-roots.local.txt
9+
* echo "/path/to/workspace2" >> scripts/dev/run-migration-roots.local.txt
10+
* bun run scripts/dev/run-migration.ts
11+
*/
12+
13+
import { existsSync } from "node:fs";
14+
import { readFile } from "node:fs/promises";
15+
import { join } from "node:path";
16+
import { loadWorkspaceMemory } from "../../src/workspace-memory.ts";
17+
18+
async function getRoots(): Promise<string[]> {
19+
// Priority 1: environment variable
20+
const envRoots = process.env.MIGRATION_RUN_ROOTS;
21+
if (envRoots) {
22+
return envRoots.split(":").filter(root => root.length > 0);
23+
}
24+
25+
// Priority 2: local file
26+
const localFile = join(import.meta.dirname, "run-migration-roots.local.txt");
27+
if (existsSync(localFile)) {
28+
const content = await readFile(localFile, "utf8");
29+
return content.trim().split("\n").filter(root => root.length > 0);
30+
}
31+
32+
// No roots configured
33+
console.log("No workspace roots configured.");
34+
console.log("Set MIGRATION_RUN_ROOTS=/path/a:/path/b or create run-migration-roots.local.txt");
35+
return [];
36+
}
37+
38+
const roots = await getRoots();
39+
40+
if (roots.length === 0) {
41+
process.exit(0);
42+
}
43+
44+
for (const root of roots) {
45+
console.log(`Loading workspace memory: ${root}`);
46+
const store = await loadWorkspaceMemory(root);
47+
const active = store.entries.filter(entry => entry.status !== "superseded").length;
48+
const superseded = store.entries.filter(entry => entry.status === "superseded").length;
49+
console.log(` active=${active} superseded=${superseded} migrations=${(store.migrations ?? []).join(",")}`);
50+
}

0 commit comments

Comments
 (0)