127 Commits

Author SHA1 Message Date
Ralph Chang 83dcfb479c fix: auto-supersede low-quality compaction memories 2026-04-28 13:29:28 +08:00
Ralph Chang ed6005f6cf fix: tighten compaction memory candidate prompt 2026-04-28 13:24:43 +08:00
Ralph Chang 069ec8ecbb fix: unify workspace memory quality gate 2026-04-28 13:21:15 +08:00
Ralph Chang 60c7019820 chore: prepare v1.3.3 release v1.3.3 2026-04-28 13:06:14 +08:00
Ralph Chang 1847f63480 fix: owner scope in global unowned promotion
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
2026-04-28 12:27:46 +08:00
Ralph Chang 8b21325469 fix: cross-process lock stale judgment and heartbeat
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
2026-04-28 12:24:56 +08:00
Ralph Chang b846b34e30 feat: implement Plan 1 - Critical Stability fixes
Wave 1: Storage and Journal Safety
- Add frozen cache TTL (1h) and size bounds (50 sessions)
- Add pending journal source-aware retention (compaction-only TTL)
- Add inter-process file lock with stale recovery
- Move processLatestUserMessage to first transform (after isSubAgent guard)

Wave 2: Promotion Ownership and Bounded Rejection
- Add pendingOwnerSessionID/pendingMessageID metadata
- Add owner-aware pending journal clearing
- Add explicit/manual bounded retry (max 3 attempts)
- Fix session.deleted cleanup idempotency

Wave 3: Normalize, Security, and Cache Hardening
- Fix load-time write loop (only write on security/migration change)
- Add deterministic sort tie-breaker (createdAt -> id)
- Add Bearer token redaction
- Add processed message cache bounds
- Remove priorityWithFreshness dead code

Tests: 180 pass, 0 fail
2026-04-28 11:59:29 +08:00
Ralph Chang 47905921ca fix: run compatibility CI on Node 24 2026-04-27 22:13:23 +08:00
Ralph Chang ca88193f9f fix: support CI installs without lockfile 2026-04-27 22:04:11 +08:00
Ralph Chang 1927cc8828 chore: prepare v1.3.1 release 2026-04-27 22:00:04 +08:00
Ralph Chang 64f86ef39c refactor: make memory dedupe repo-agnostic 2026-04-27 21:19:42 +08:00
Ralph Chang 39d27e8d3c docs: note PR 3 security hardening 2026-04-27 20:22:26 +08:00
Ralph Chang 77bf8af3fe test: cover security hardening edge cases 2026-04-27 20:22:09 +08:00
Ralph Chang 6eb341f43c merge: integrate PR #3 security hardening 2026-04-27 20:14:08 +08:00
Ralph Chang 6a1fa525dc docs: document concise compatibility limitations 2026-04-27 19:57:21 +08:00
Ralph Chang d6875aac1b fix: cap and prune pending memory journal 2026-04-27 18:54:44 +08:00
Ralph Chang c2ee245620 test: add opencode plugin compatibility checks 2026-04-27 18:54:14 +08:00
Steven Choo 15c0c8a45d feat: implement indirect prompt injection protection and expanded secret redaction 2026-04-27 12:42:20 +02:00
Ralph Chang 909fec9abb docs: prepare v1.3.0 release notes 2026-04-27 17:06:43 +08:00
Ralph Chang ef1248f23a feat: add consolidation accounting for workspace memory promotion
P0 implementation with four waves:

Wave 1: Dedup with accounting
- Add dedupeLongTermEntriesWithAccounting()
- Classify exact duplicate, identity duplicate, topic duplicate

Wave 2: Normalization with accounting
- Add normalizeWorkspaceMemoryWithAccounting()
- Chain redaction → migration → enforceLongTermLimitsWithAccounting

Wave 3: Promotion accounting integration
- Update accountPendingPromotions() to use new accounting API
- Add supersededKeys to classification
- Distinguish promoted / absorbed / superseded / rejected

Wave 4: Integration tests
- End-to-end tests covering full pipeline

Bug fixes:
- Fix active vs superseded boundary (superseded entries no longer block promotion)
- Remove unused rejected_duplicate_lower_quality type
- Defer pending journal safety cap (TODO added)

Tests: 135 passing (up from 115)
2026-04-27 16:45:55 +08:00
Ralph Chang c8c7dbed3b chore: ignore superpowers plans and update architecture doc
- 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
2026-04-27 14:53:07 +08:00
Ralph Chang bfa2972353 feat: sharpen compaction memory extraction prompt
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'
2026-04-27 14:40:32 +08:00
Ralph Chang 5fe4955057 test: add memory quality eval fixtures
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
2026-04-27 14:34:53 +08:00
Ralph Chang 55e163adef fix: account for absorbed pending memories
- Add workspaceMemoryIdentityKey() to unify dedup/supersession identity semantics
- Add accountPendingPromotions() to distinguish promoted/absorbed/rejected
- Wire promotion accounting into promotePendingMemories()
- Add clearableKeys.size > 0 guard to prevent journal wipe
- Add regression tests for absorbed duplicate, cap-rejected, all-rejected edge cases

Wave 1 of memory quality optimization plan.
2026-04-27 14:27:43 +08:00
Ralph Chang 5ed57943d2 docs: add memory quality optimization implementation plan
P0 implementation plan with 3 waves:
- Wave 1: Promotion accounting (fix absorbed duplicate data loss)
- Wave 2: Memory quality eval (fixture-based regression guard)
- Wave 3: Compaction prompt negative examples

Addresses architecture review feedback:
- Distinguish promoted/absorbed/rejected pending memories
- Add clearableKeys.size > 0 guard to prevent journal wipe
- Add regression tests for all edge cases
2026-04-27 14:16:46 +08:00
sdwolf4103 2fc2172d59 Fix formatting in README.md 2026-04-27 13:00:26 +08:00
Ralph Chang fd8d730e3b docs: streamline README and document ongoing work 2026-04-27 12:38:11 +08:00
Ralph Chang 4309cb855f fix: promotion accounting, sessionID extraction, and strengthened regression tests
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.
2026-04-27 10:02:18 +08:00
Ralph Chang 2437a9dc71 fix: clarify cache epoch semantics and add regression tests
- Update plugin.ts comments to describe 'session cache epoch' instead
  of misleading 'session lifetime' wording
- Add regression test: same-session explicit memory does not mutate
  frozen system[1]; pending memory goes to ephemeral system[2+]
- Add regression test: session.compacted intentionally refreshes
  system[1] as a new cache epoch boundary (promotes pending memories,
  clears frozen cache, next transform re-renders workspace memory)
- Both tests use one plugin instance with mutable mock client to
  preserve in-memory frozen cache across turns
2026-04-27 09:55:03 +08:00
Ralph Chang 3560868f52 release: v1.2.3 v1.2.3 2026-04-27 02:24:48 +08:00
Ralph Chang e7c7a5cfb2 feat: add durable pending memory journal 2026-04-27 02:20:26 +08:00
Ralph Chang 026c75a5e4 feat: freeze rendered workspace memory snapshot 2026-04-27 01:57:41 +08:00
Ralph Chang eb74a9f03e docs: add workspace memory cache optimization plan 2026-04-27 01:55:48 +08:00
Ralph Chang f6f35e87c1 feat: release v1.2.2 with multilingual memory hardening v1.2.2 2026-04-27 00:21:18 +08:00
Ralph Chang 6603fe869d docs: add v1.2.1 release notes v1.2.1 2026-04-26 16:58:14 +08:00
Ralph Chang 3d44269228 fix: resolve remaining architect issues - split feedback keys, remove generic config key, supersession mode
- Split feedbackTopicKey: server-error now separate from port-occupied-environment
- Remove generic plugin.*config entity key (too broad), fall back to canonical dedup
- Feedback topic conflicts now use supersession mode (newer beats longer)
- Add 3 regression tests: English port/split, unrelated configs, feedback supersession

70/70 tests pass.
2026-04-26 16:54:24 +08:00
Ralph Chang a154139b27 fix: P0c/P0d architect review corrections
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.
2026-04-26 16:50:58 +08:00
Ralph Chang 7527765207 feat: storage-time dedupe, stale pruning, and supersession (P0d)
- Project/reference entries dedupe by entity key (bilingual aware)
- Decision entries supersede by topic key (parser formats, template, etc)
- Feedback entries supersede by topic key (same issue, newer fix wins)
- Stale compaction/manual entries pruned after staleAfterDays + 30
- Explicit and feedback entries never age-pruned
- Freshness used as tie-breaker in priority-based trimming
- Adds 10 new tests covering dedup, supersession, staleness, and freshness
2026-04-26 16:37:18 +08:00
Ralph Chang f9acfd6136 fix: parser accepts bracketless format, rejects project snapshots, adds durable-content prompt
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
2026-04-26 16:28:55 +08:00
Ralph Chang ca71c20a8f docs: add memory dedup & staleness architecture analysis 2026-04-26 16:20:29 +08:00
Ralph Chang 5e9ada6859 fix: replace default compaction template to prevent purple italic rendering
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
2026-04-26 15:46:41 +08:00
Ralph Chang 721544e7a8 fix: use plain text labels instead of Markdown headers
- 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.
2026-04-26 15:13:58 +08:00
Ralph Chang 32fa2bd454 chore: keep version at 1.2.1 2026-04-26 14:50:19 +08:00
Ralph Chang af539a42f3 chore: bump version to 1.2.2 for HTML comment output format 2026-04-26 14:49:46 +08:00
Ralph Chang eff0d3784c fix: change compaction output to HTML comment, prevent Markdown rendering issues
Root cause: Model was instructed to output <workspace_memory_candidates> XML
tags in the user-visible compaction summary, causing purple/italic rendering
when combined with --- delimiters in Markdown.

Fixes:
- compactionContextHeader(): Now instructs model to use HTML comment format
  <!-- workspace_memory_candidates ... --> which is hidden from users
- extractCandidateBlock(): New function supports 3 formats:
  1. HTML comment (preferred, hidden from user)
  2. Markdown section (visible but clean)
  3. Legacy XML (backward compatible)
- Added "DO NOT use XML tags" and "DO NOT start with ---" instructions

Tests:
- Verify compaction context header uses HTML comment format
- Test parser accepts all 3 formats (HTML comment, Markdown, legacy XML)
2026-04-26 14:49:38 +08:00
Ralph Chang 2354b62350 chore: bump version to 1.2.1 for compaction context fix 2026-04-26 14:35:18 +08:00
Ralph Chang 92e90124de fix: prevent XML tags in compaction context from causing Markdown rendering issues
- Add stripXmlTags() to convert <workspace_memory>, <hot_session_state>, <pending_todos> to Markdown headers for compaction context
- Add [PRIVATE COMPACTION CONTEXT - DO NOT OUTPUT] wrapper to prevent model from copying input context to output
- Rename renderTodos to renderTodosForCompaction for clarity
- Add test to verify compaction context contains no XML tags

This fixes the issue where compaction summary would render with purple italic text
due to --- delimiters interacting with XML-like tags in Markdown.
2026-04-26 14:34:55 +08:00
Ralph Chang 22774c5ed2 docs: add RELEASE_NOTES.md for v1.2.0 v1.2.0 2026-04-26 14:18:34 +08:00
Ralph Chang 9892012d8b chore: prepare for v1.2.0 release
- Bump version to 1.2.0
- Add package.json exports and files whitelist
- Update .gitignore to exclude .opencode/ and .opencode-agenthub/
- Fix docs: active files tracked in tool.execute.after (not before)
- Fix docs: exitCode undefined is ignored (not success)
- Fix docs: session ID is hashed in storage path
- Fix docs: workspace memory survives within same workspace
2026-04-26 14:17:54 +08:00
Ralph Chang f988af4453 docs: enhance README with visual three-layer architecture diagram
- Add table showing layer scope, tracking, and persistence
- Add visual ASCII diagram emphasizing cross-session capability
- Add compaction flow diagram showing no extra API call
- Highlight piggyback on existing compaction summary
2026-04-26 13:43:06 +08:00