Problem: clearPendingMemories() and recordPromotionRejections() would
incorrectly clear or mutate owned entries during global unowned promotion.
Fixes:
1. clearPendingMemories() now respects owner/unowned scope:
- global clearUnowned only clears unowned same-key entries
- owned same-key entries are preserved
- explicit global clear-all-by-key fallback still works
2. recordPromotionRejections() now has includeUnownedOnly option:
- global unowned rejection only increments/exhausts unowned entries
- owned same-key entries are preserved
3. Added regression tests:
- global unowned clear keeps owned same-key entries
- global unowned rejection only exhausts unowned same-key entries
Tests: 182 pass, 0 fail
Problem: CI test "updateJSON serializes writes across separate node processes"
was failing with expect 100 but got 89/97. The root cause was isLockStale()
being too aggressive - it could mistakenly delete locks held by other processes.
Fixes:
1. isLockStale() now uses mtime only - fresh locks are never stale
2. Added heartbeat mechanism during lock hold to support long updaters
3. Removed PID check that was unreliable in CI/containers
4. Fixed ENOENT race when lock is released between EEXIST and stat
Tests: 180 pass, 0 fail
- Add docs/superpowers/plans/ to .gitignore
- Remove tracked plan files from git
- Update docs/architecture.md:
- Change primary extraction format from XML to 'Memory candidates:'
- Mark XML format as legacy/deprecated
- Fix hot session state injection example
Wave 3 of memory quality optimization plan.
- Add good memory examples in buildCompactionPrompt()
- Add bad memory examples to skip (test counts, commit hashes, etc.)
- Add prompt assertions in tests to prevent regression
- Emphasize 'useful if a new agent opens this workspace next week'
Wave 2 of memory quality optimization plan.
- 5 accepted cases: durable facts that should be kept
- 7 rejected cases: noise that should be filtered
- Parser-level regression guard (zero API call)
- All cases pass against current extractors.ts
Architecture review fixes:
- Promotion accounting: only clear pending memories that survived
workspace memory normalization/cap limits. Use retainedKeys from
the returned normalized store instead of attemptedKeys.
- Shared sessionID extraction: add sessionIDFromEventProperties()
helper and use it in both session.compacted and session.deleted,
fixing the previous gap where session.deleted only read info.id.
- Strengthen compaction refresh test: seed workspace memory before
first transform so firstSystem1 is non-empty, then assert
refreshed system[1] preserves existing entries AND contains
promoted memories.
P0c fixes:
- Chinese file count regex now accepts 個/个 between number and 文件
- Admin PIN short reference (<20 chars) passes via config value allowlist
- Phase snapshot uses semantic window (.{0,20}) instead of absolute position
P0d fixes:
- Feedback key split: 500 error and port issue remain separate entries
- extractEntityKey avoids over-merging unrelated plugin configs
- chooseBetterMemory supports supersession mode (newer beats longer)
- Sort comparator now includes source priority as secondary tie-breaker
New regression tests (11 total):
- Real Admin PIN short reference passes
- Real Chinese 37 個文件 snapshot rejected
- Real pathology Phase 1-4 snapshot rejected
- Feedback 500 vs port entries not collapsed
- Unrelated plugin configs not collapsed
- Supersession prefers newer shorter over older longer
67/67 tests pass.
P0a: Parser now accepts both - [type] text and - type text formats
P0b: Prompt adds durable-content guidance to avoid session-specific snapshots
P0c: Parser quality gate rejects exact test counts, file counts, phase progress
- Only rejects phase progress when it appears early in the string (snapshot)
- Stable config values with numbers (Admin PIN, Scrypt) still pass
- Adds 7 new tests covering bracketless parsing and snapshot rejection
Root cause: OpenCode's default compaction template uses --- separators.
When our plugin adds structured context (Memory candidates: format), the
model strictly follows the template, outputting --- at position 0. The
markdown textmate grammar treats this as YAML frontmatter, applying the
'comment' syntax scope (purple + italic in themes like palenight).
Fix: Set output.prompt in the compacting hook to replace the entire
template with a ---free version. Uses only ## Markdown headings and
explicitly forbids YAML frontmatter, horizontal rules, and delimiter
lines. Preserves context from other plugins by merging output.context.
- Replace compactionContextHeader() with buildCompactionPrompt()
- Set output.prompt instead of pushing to output.context
- Merge existing output.context from other plugins before clearing
- Add 'Instructions' section to the template (per architect review)
- Update tests: verify output.prompt, ---free format, context merging
- Changed '## Memory Candidates' to 'Memory candidates:' in compaction context
- Changed '## Pending Todos' to 'Pending todos:' in todo rendering
- Updated extractCandidateBlock() to parse plain text format (primary)
- Removed stripXmlTags() function (no longer needed)
- All 42 tests pass
Root cause: Markdown headings (##) render as purple in OpenCode UI,
same issue as XML tags and HTML comments. Plain text labels avoid
all special markup rendering.