feat(memory-diag): add quality review board command

Add memory-diag quality command for objective review of memory-system
mechanisms and active memory content. The command is read-only and
non-authoritative, providing evidence, heuristic flags, and review
questions without making quality judgments or suggesting mutations.

Key components:
- quality-review-model.ts: builds ReviewBoardReport with provenance,
  re-absorption detection, mechanism facts (rejection, reinforcement,
  eviction/caps, identity/dedup), and memory content facts
- formatters/quality.ts: human and JSON output with separate
  system-mechanism and memory-content sections
- commands/quality.ts: command entry point with --json, --verbose,
  --no-emoji, --raw options
- cli.ts: parser whitelist for quality accepting --workspace, --json,
  rejecting mutation/filter flags

co-author: code-execute-agent, comprehensive-code-reviewer,
systems-architect, creative-disruptor

Closes docs/plans/2026-05-11-memory-diag-quality-review-board.md
This commit is contained in:
Ralph Chang
2026-05-12 14:13:03 +08:00
parent e4dfe81d89
commit 5bca3432b0
11 changed files with 1953 additions and 7 deletions
+2 -1
View File
@@ -248,6 +248,7 @@ npx --package opencode-working-memory memory-diag status
npx --package opencode-working-memory memory-diag rejected
npx --package opencode-working-memory memory-diag missing
npx --package opencode-working-memory memory-diag explain <memory-id>
npx --package opencode-working-memory memory-diag quality
```
See [Diagnostics](docs/diagnostics.md) for the full command reference, numbered memory command reports, and dry-run recovery workflow.
@@ -276,7 +277,7 @@ See [Configuration](docs/configuration.md) for customization options.
## Requirements
- OpenCode plugin API `>=1.2.0 <2.0.0`
- Node.js >= 22.6.0 (for `memory-diag` CLI, which runs TypeScript with `--experimental-strip-types`)
- Node.js >= 22.6.0 (the published `memory-diag` CLI runs compiled JavaScript)
## Limitations
+14
View File
@@ -13,6 +13,7 @@ The npm package is `opencode-working-memory`; the installed bin is `memory-diag`
| Where did my memory go? | `npx --package opencode-working-memory memory-diag missing` |
| Why is this memory shown or hidden? | `npx --package opencode-working-memory memory-diag explain <memory-id>` |
| How are numbered memory commands behaving? | `npx --package opencode-working-memory memory-diag commands` |
| How do I review memory quality without automatic cleanup? | `npx --package opencode-working-memory memory-diag quality` |
| Revert a numbered replacement? | `npx --package opencode-working-memory memory-diag revert --memory <replacement-memory-id>` |
## Global Options
@@ -29,9 +30,22 @@ npx --package opencode-working-memory memory-diag rejected --verbose
npx --package opencode-working-memory memory-diag missing --workspace /path/to/project
npx --package opencode-working-memory memory-diag status --json
npx --package opencode-working-memory memory-diag commands --verbose
npx --package opencode-working-memory memory-diag quality
npx --package opencode-working-memory memory-diag revert --memory <replacement-memory-id>
```
## Quality Review Board
Use `memory-diag quality` for a read-only, evidence-first review of memory quality without automatic cleanup.
- Primarily provides memory-system mechanism observations for human/agent interpretation.
- Secondarily helps review active memory content quality.
- Separates system-mechanism facts, memory-content facts, heuristic flags, and review questions.
- Includes inferred evidence provenance because historical records do not record producer package version.
- Labels uncertain provenance as `unversioned_ambiguous` so old artifacts are not treated as current mechanism failures.
- Does not decide what to delete or mutate.
- Use `--json` for agent/objective review.
## Numbered Memory Command Reports
Use `memory-diag commands` to inspect `REINFORCE [M#]` and `REPLACE [M#]` outcomes from compaction.
+3 -2
View File
@@ -10,6 +10,7 @@ export function usage(): string {
memory-diag missing [--workspace <path>] [--verbose] [--json]
memory-diag explain [memory-id] [--workspace <path>] [--raw]
memory-diag commands [--workspace <path>] [--verbose] [--json]
memory-diag quality [--workspace <path>] [--verbose] [--json] [--raw] [--no-emoji]
memory-diag revert (--memory <replacement-id> | --event <event-id>) [--workspace <path>] [--apply]
Global options:
@@ -103,12 +104,12 @@ export function parseArgs(argv: string[]): ParsedArgs {
if (command === "status") {
if (options.all) return error(`${command} does not accept --all`);
} else if (command === "rejected" || command === "missing" || command === "coverage" || command === "explain" || command === "commands" || command === "revert") {
} else if (command === "rejected" || command === "missing" || command === "coverage" || command === "explain" || command === "commands" || command === "quality" || command === "revert") {
if (options.all) return error(`${command} does not accept --all`);
} else {
if (options.all || options.workspace) return error(`${command} does not accept --all or --workspace`);
}
if (options.json && command !== "status" && command !== "rejected" && command !== "missing" && command !== "coverage" && command !== "commands") {
if (options.json && command !== "status" && command !== "rejected" && command !== "missing" && command !== "coverage" && command !== "commands" && command !== "quality") {
return error(`${command} does not accept --json`);
}
if (command !== "rejected" && (options.softOnly || options.triggerOnly || options.since)) {
+1 -1
View File
@@ -1,4 +1,4 @@
export const VISIBLE_COMMANDS = ["status", "rejected", "missing", "explain", "commands", "revert"] as const;
export const VISIBLE_COMMANDS = ["status", "rejected", "missing", "explain", "commands", "quality", "revert"] as const;
export const HIDDEN_COMMANDS = ["coverage", "audit"] as const;
export type VisibleCommand = typeof VISIBLE_COMMANDS[number];
+2
View File
@@ -3,6 +3,7 @@ import { runCommands } from "./commands/commands.ts";
import { runCoverage } from "./commands/coverage.ts";
import { runExplain } from "./commands/explain.ts";
import { runMissing } from "./commands/missing.ts";
import { runQuality } from "./commands/quality.ts";
import { runRejected } from "./commands/rejected.ts";
import { runRevert } from "./commands/revert.ts";
import { runStatus } from "./commands/status.ts";
@@ -17,6 +18,7 @@ export async function dispatch(command: Command, options: CliOptions): Promise<C
case "audit": return runAudit(options);
case "explain": return runExplain(options);
case "commands": return runCommands(options);
case "quality": return runQuality(options);
case "revert": return runRevert(options);
}
}
+20
View File
@@ -0,0 +1,20 @@
import { buildQualityJSON, formatQualityReviewBoard } from "../formatters/quality.ts";
import { buildInspectionReadModel } from "../inspection-model.ts";
import { buildQualityReviewBoard } from "../quality-review-model.ts";
import type { CliOptions, CommandResult } from "../types.ts";
export async function runQuality(options: CliOptions): Promise<CommandResult> {
const model = await buildInspectionReadModel(options);
const report = buildQualityReviewBoard(model, {
verbose: options.verbose,
raw: options.raw,
noEmoji: options.noEmoji,
json: options.json,
});
if (options.json) {
return { stdout: JSON.stringify(buildQualityJSON(report, options.raw), null, 2) };
}
return { stdout: formatQualityReviewBoard(report, { verbose: options.verbose, noEmoji: options.noEmoji }) };
}
+314
View File
@@ -0,0 +1,314 @@
import { cleanText, formatDetails } from "../text.ts";
import type {
CandidateProvenance,
HeuristicFlag,
ProvenanceClassification,
ReviewBoardActiveMemory,
ReviewBoardCandidate,
ReviewBoardReport,
} from "../quality-review-model.ts";
const PROVENANCE_ORDER: ProvenanceClassification[] = [
"explicit_migration_evidence",
"legacy_unversioned_format",
"reabsorbed_post_rejection",
"suspected_pre_migration_legacy",
"likely_current_behavior",
"unversioned_ambiguous",
];
const REVIEW_FLAG_CAVEAT = "This flag is a prompt for review, not a conclusion.";
export function buildQualityJSON(report: ReviewBoardReport, raw = false): unknown {
if (raw) return report;
return redactUnknown(report);
}
export function formatQualityReviewBoard(
report: ReviewBoardReport,
options: { verbose?: boolean; noEmoji?: boolean },
): string {
const bullet = "-";
const lines: string[] = [];
lines.push("Memory quality review board");
lines.push("Purpose: evidence for human/agent review only; no automatic judgment or cleanup.");
lines.push("Producer version note: historical records do not include package/plugin version; provenance below is inferred.");
lines.push("Primary review purpose: SYSTEM MECHANISM observations (filters, reinforcement, eviction/caps, identity/dedup).");
lines.push("Secondary review purpose: MEMORY CONTENT quality (staleness, durability, redundancy, specificity).");
lines.push("");
pushEvidenceProvenance(lines, report, bullet);
lines.push("");
pushSystemMechanismFacts(lines, report, bullet);
lines.push("");
pushMemoryContentFacts(lines, report, bullet);
lines.push("");
pushSystemMechanismCandidates(lines, report, bullet);
lines.push("");
pushMemoryContentCandidates(lines, report, bullet, options);
lines.push("");
pushReviewQuestions(lines, report, bullet);
lines.push("");
pushNextCommands(lines, report, bullet);
return lines.join("\n");
}
function pushEvidenceProvenance(lines: string[], report: ReviewBoardReport, bullet: string): void {
const context = report.provenanceContext;
lines.push("Evidence provenance");
lines.push(` ${bullet} method: migration/timestamp/format inference`);
lines.push(` ${bullet} confidence: ${context.confidenceDisclaimer}`);
lines.push(` ${bullet} migration timeline: ${formatMigrationTimeline(context.migrationTimeline)}`);
if (context.lastActivityAt) lines.push(` ${bullet} last activity: ${context.lastActivityAt}`);
}
function pushSystemMechanismFacts(lines: string[], report: ReviewBoardReport, bullet: string): void {
const facts = report.facts.systemMechanisms;
lines.push("Facts - system mechanisms");
lines.push(" Provenance counts for mechanism evidence");
lines.push(` ${bullet} ${formatProvenanceCounts(report.provenanceContext.countsByClassification)}`);
lines.push(" Rejection filters");
lines.push(` ${bullet} rejected records: ${facts.rejectionFilters.totalRecords} (unique: ${facts.rejectionFilters.uniqueTexts})`);
lines.push(` ${bullet} raw reason-code distribution: ${formatCounts(facts.rejectionFilters.byRawReasonCode)}`);
lines.push(` ${bullet} type distribution: ${formatCounts(facts.rejectionFilters.byType)}`);
lines.push(` ${bullet} ambiguous/architecture-like rejected candidates: ${facts.rejectionFilters.ambiguousOrArchitectureLike}`);
lines.push(` ${bullet} status-or-hard-reason evidence: ${facts.rejectionFilters.hardReasonOrNoiseHeuristic}`);
lines.push(` ${bullet} re-absorbed rejected texts: ${facts.rejectionFilters.reabsorbedRejectedTexts}`);
lines.push(" Reinforcement rules");
lines.push(` ${bullet} reinforce attempts: ${facts.reinforcementRules.reinforceEvents}, reinforced: ${facts.reinforcementRules.reinforcedEvents}, rejected/blocked: ${facts.reinforcementRules.rejectedOrBlockedEvents}`);
lines.push(` ${bullet} reinforcement-window blocked: ${facts.reinforcementRules.windowBlockedEvents} (rate: ${formatPercent(facts.reinforcementRules.windowBlockRate)})`);
lines.push(` ${bullet} repeated blocks by memory: ${formatRepeatedBlocks(facts.reinforcementRules.repeatedBlocksByMemory)}`);
lines.push(` ${bullet} malformed command events: ${facts.reinforcementRules.malformedCommandEvents}`);
lines.push(" Eviction and caps");
lines.push(` ${bullet} active memories: ${facts.evictionAndCaps.activeMemories} / ${facts.evictionAndCaps.maxEntries}`);
lines.push(` ${bullet} rendered memories: ${facts.evictionAndCaps.renderedMemories}`);
lines.push(` ${bullet} full caps: ${formatFullCaps(facts.evictionAndCaps.fullCaps, facts.evictionAndCaps.typeCounts, facts.evictionAndCaps.typeCaps, facts.evictionAndCaps.activeMemories, facts.evictionAndCaps.maxEntries)}`);
lines.push(` ${bullet} capacity removals: total=${facts.evictionAndCaps.removedByCapacity}, global=${facts.evictionAndCaps.removedByGlobalCap}, type=${facts.evictionAndCaps.removedByTypeCap}`);
lines.push(` ${bullet} recent evictions by type: ${formatCounts(facts.evictionAndCaps.recentEvictionsByType)}`);
lines.push(` ${bullet} recent evicted content shown: ${facts.evictionAndCaps.recentEvictedContentShown}`);
lines.push(` ${bullet} evidence-only disappearances: ${facts.evictionAndCaps.missingEvidenceOnly} (unknown: ${facts.evictionAndCaps.unknownDisappearances})`);
lines.push(" Identity and dedup");
lines.push(` ${bullet} replacements: total=${facts.identityAndDedup.replacementEvents}, same-type=${facts.identityAndDedup.sameTypeReplacementEvents}, cross-type=${facts.identityAndDedup.crossTypeReplacementEvents}`);
lines.push(` ${bullet} superseded entries: ${facts.identityAndDedup.supersededEntries}`);
lines.push(` ${bullet} exact duplicate/identity groups identified: ${facts.identityAndDedup.duplicateTextOrIdentityGroups}`);
}
function pushMemoryContentFacts(lines: string[], report: ReviewBoardReport, bullet: string): void {
const facts = report.facts.memoryContent;
lines.push("Facts - memory content");
lines.push(` ${bullet} rendered memories: ${facts.renderedMemories}`);
lines.push(` ${bullet} evidence coverage: ${facts.evidenceCoverage.covered} / ${facts.evidenceCoverage.total}`);
lines.push(` ${bullet} type counts: ${formatTypeCountsWithCaps(facts.typeCounts, facts.typeCaps)}`);
lines.push(` ${bullet} weakest/strongest active memory previews: weakest=${formatMemoryPreviews(facts.weakestActiveMemories)}; strongest=${formatMemoryPreviews(facts.strongestActiveMemories)}`);
}
function pushSystemMechanismCandidates(lines: string[], report: ReviewBoardReport, bullet: string): void {
const display = report.provenanceContext.candidateDisplay;
if (report.provenanceContext.candidateLimit && display && display.shown < display.total) {
lines.push(`System mechanism review candidates (representative; ${display.shown} shown of ${display.total} total; limit ${report.provenanceContext.candidateLimit} per mechanism category)`);
} else {
lines.push("System mechanism review candidates");
}
pushCandidateGroup(lines, "Rejection filter evidence", candidatesFor(report, ["rejection_rule_evidence"]), bullet);
pushCandidateGroup(lines, "Re-absorption evidence", candidatesFor(report, ["reabsorption_evidence"]), bullet);
pushCandidateGroup(lines, "Reinforcement rule evidence", candidatesFor(report, ["numbered_command_evidence"]), bullet);
pushCandidateGroup(lines, "Eviction/cap evidence", candidatesFor(report, ["eviction_cap_evidence", "missing_evidence"]), bullet);
pushCandidateGroup(lines, "Identity/dedup evidence", candidatesFor(report, ["identity_dedup_evidence"]), bullet);
}
function pushCandidateGroup(lines: string[], title: string, candidates: ReviewBoardCandidate[], bullet: string): void {
lines.push(` ${title}`);
if (candidates.length === 0) {
lines.push(" (none)");
return;
}
const shared = sharedProvenance(candidates);
if (shared) lines.push(` shared provenance for displayed candidates in this group: ${formatProvenance(shared)}`);
for (const candidate of candidates) pushCandidate(lines, candidate, bullet, shared);
}
function pushCandidate(lines: string[], candidate: ReviewBoardCandidate, bullet: string, groupProvenance?: CandidateProvenance): void {
const rawReasonCodes = candidate.evidence.rawReasonCodes && candidate.evidence.rawReasonCodes.length > 0
? candidate.evidence.rawReasonCodes.join(", ")
: "none";
const question = candidate.reviewQuestions[0] ?? "What should a reviewer infer from this evidence?";
lines.push(` ${bullet} concern=${formatConcern(candidate.concernKind)} id=${candidate.id} source=${candidate.source} mechanism=${candidate.mechanism ?? "unspecified"} raw reason codes=${rawReasonCodes} question=${question}`);
if (candidate.provenance && (!groupProvenance || formatProvenance(candidate.provenance) !== formatProvenance(groupProvenance))) {
lines.push(` provenance: ${formatProvenance(candidate.provenance)}`);
}
if (candidate.evidence.eventIds && candidate.evidence.eventIds.length > 0) lines.push(` event ids: ${candidate.evidence.eventIds.join(", ")}`);
if (candidate.evidence.textAvailable) {
lines.push(` text preview: ${candidate.evidence.textPreview ?? "available but empty after redaction"}`);
} else {
lines.push(" text preview: unavailable in historical evidence");
}
lines.push(` facts: ${formatCandidateFacts(candidate.facts)}`);
pushHeuristicFlags(lines, candidate.heuristicFlags, " ", bullet);
}
function pushMemoryContentCandidates(lines: string[], report: ReviewBoardReport, bullet: string, options: { verbose?: boolean }): void {
const display = report.activeMemoryDisplay;
lines.push("Memory content review candidates");
if (report.reviewQuestions.memoryContent.length > 0) {
lines.push(" Standard review questions (applicable to all active memories below):");
for (const question of report.reviewQuestions.memoryContent) lines.push(` ${bullet} ${question}`);
}
if (display.total === 0) {
lines.push(" Active memories (none)");
return;
}
if (display.total <= display.threshold) {
lines.push(` Active memories (showing all ${display.total} because <= ${display.threshold})`);
} else if (display.mode === "all" || options.verbose) {
lines.push(` Active memories (showing all ${display.total} because --verbose)`);
} else {
lines.push(` Active memories (showing ${display.shown} of ${display.total})`);
lines.push(` Showing ${display.shown} of ${display.total} active memories. Use --verbose or --json for all active memory text.`);
}
display.items.forEach((item, index) => pushActiveMemory(lines, item, index + 1, bullet, report.reviewQuestions.memoryContent));
}
function pushActiveMemory(lines: string[], item: ReviewBoardActiveMemory, index: number, bullet: string, standardQuestions: string[]): void {
const strength = typeof item.strength === "number" ? item.strength.toFixed(3) : "unknown";
lines.push(` [${index}] id=${item.id} type=${item.type} source=${item.source} status=${item.status} strength=${strength}`);
lines.push(" text: " + indentContinuation(item.text, " "));
const rawReasonCodes = item.evidence.rawReasonCodes.length > 0 ? item.evidence.rawReasonCodes.join(", ") : "none";
lines.push(` evidence: events=${item.evidence.eventCount} raw reason codes=${rawReasonCodes}`);
if (item.provenance) lines.push(` provenance: ${formatProvenance(item.provenance)}`);
pushHeuristicFlags(lines, item.heuristicFlags, " ", bullet);
if (questionsEqual(item.reviewQuestions, standardQuestions)) return;
const additionalQuestions = item.reviewQuestions.filter(question => !standardQuestions.includes(question));
if (additionalQuestions.length > 0 && additionalQuestions.length < item.reviewQuestions.length) {
lines.push(" additional review questions:");
for (const question of additionalQuestions) lines.push(` ${bullet} ${question}`);
return;
}
lines.push(" review questions:");
for (const question of item.reviewQuestions) lines.push(` ${bullet} ${question}`);
}
function pushHeuristicFlags(lines: string[], flags: HeuristicFlag[], indent: string, bullet: string): void {
if (flags.length === 0) return;
lines.push(`${indent}heuristic flags:`);
for (const flag of flags) {
const caveat = flag.caveat || REVIEW_FLAG_CAVEAT;
lines.push(`${indent} ${bullet} ${flag.label}: ${flag.evidence}. ${caveat}`);
}
}
function pushReviewQuestions(lines: string[], report: ReviewBoardReport, bullet: string): void {
lines.push("Review questions");
lines.push(" SYSTEM MECHANISM");
for (const question of report.reviewQuestions.systemMechanism) lines.push(` ${bullet} ${question}`);
lines.push(" MEMORY CONTENT");
for (const question of report.reviewQuestions.memoryContent) lines.push(` ${bullet} ${question}`);
}
function pushNextCommands(lines: string[], report: ReviewBoardReport, bullet: string): void {
lines.push("Next commands");
for (const command of report.nextCommands) lines.push(` ${bullet} ${command}`);
}
function candidatesFor(report: ReviewBoardReport, sources: ReviewBoardCandidate["source"][]): ReviewBoardCandidate[] {
const sourceSet = new Set(sources);
return report.reviewCandidates.filter(candidate => candidate.concernKind === "system_mechanism" && sourceSet.has(candidate.source));
}
function sharedProvenance(candidates: ReviewBoardCandidate[]): CandidateProvenance | undefined {
if (candidates.length <= 1) return undefined;
const first = candidates[0]?.provenance;
if (!first) return undefined;
const key = formatProvenance(first);
return candidates.every(candidate => candidate.provenance && formatProvenance(candidate.provenance) === key) ? first : undefined;
}
function formatConcern(concern: ReviewBoardCandidate["concernKind"]): string {
return concern === "system_mechanism" ? "SYSTEM MECHANISM" : "MEMORY CONTENT";
}
function formatMigrationTimeline(timeline: ReviewBoardReport["provenanceContext"]["migrationTimeline"]): string {
if (timeline.length === 0) return "(none)";
return timeline.map(row => `${row.migrationId}=${row.presentInStore ? "present" : "absent"}${row.firstEvidenceAt ? ` firstEvidenceAt=${row.firstEvidenceAt}` : ""}`).join(", ");
}
function formatProvenanceCounts(counts: Record<ProvenanceClassification, number>): string {
return PROVENANCE_ORDER.map(classification => `${classification}=${counts[classification] ?? 0}`).join(", ");
}
function formatCounts(counts: Record<string, number>): string {
const entries = Object.entries(counts).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
return entries.length === 0 ? "(none)" : entries.map(([key, count]) => `${key}=${count}`).join(", ");
}
function formatTypeCountsWithCaps(counts: Record<string, number>, caps: Record<string, number>): string {
const keys = uniqueSorted([...Object.keys(caps), ...Object.keys(counts)]);
return keys.length === 0 ? "(none)" : keys.map(key => `${key} ${counts[key] ?? 0}/${caps[key] ?? "?"}`).join(", ");
}
function formatFullCaps(fullCaps: string[], typeCounts: Record<string, number>, typeCaps: Record<string, number>, active: number, maxEntries: number): string {
if (fullCaps.length === 0) return "(none)";
return fullCaps.map(cap => cap === "global" ? `global ${active}/${maxEntries}` : `${cap} ${typeCounts[cap] ?? 0}/${typeCaps[cap] ?? "?"}`).join(", ");
}
function formatRepeatedBlocks(blocks: ReviewBoardReport["facts"]["systemMechanisms"]["reinforcementRules"]["repeatedBlocksByMemory"]): string {
if (blocks.length === 0) return "(none)";
return blocks.map(block => `${block.memoryId} count=${block.count} refs=${block.refs.join("|") || "none"} raw reason codes=${block.rawReasonCodes.join("|") || "none"}`).join(", ");
}
function formatMemoryPreviews(items: ReviewBoardReport["facts"]["memoryContent"]["weakestActiveMemories"]): string {
if (items.length === 0) return "(none)";
return items.map(item => `${item.id} type=${item.type} strength=${typeof item.strength === "number" ? item.strength.toFixed(3) : "unknown"} text=${JSON.stringify(item.textPreview)}`).join(" | ");
}
function formatPercent(value: number): string {
return `${(Number.isFinite(value) ? value * 100 : 0).toFixed(1)}%`;
}
function formatProvenance(provenance: CandidateProvenance): string {
return `${provenance.classification} confidence=${provenance.confidence}; basis=${provenance.basis.join("; ") || "unavailable"}; caveat=${provenance.interpretationCaveat}`;
}
function formatCandidateFacts(facts: Record<string, unknown>): string {
if (Object.keys(facts).length === 0) return "(none)";
return formatDetails(Object.fromEntries(
Object.entries(facts).map(([key, value]) => [key, formatFactValue(value)]),
));
}
function formatFactValue(value: unknown): string | number | boolean | string[] | undefined {
if (value === undefined) return undefined;
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
if (value === null) return "null";
if (Array.isArray(value)) return value.map(item => typeof item === "string" || typeof item === "number" || typeof item === "boolean" || item === null ? String(item) : stringifyUnknown(item));
return stringifyUnknown(value);
}
function stringifyUnknown(value: unknown): string {
try {
const serialized = JSON.stringify(value);
return serialized === undefined ? String(value) : serialized;
} catch {
return String(value);
}
}
function questionsEqual(a: string[], b: string[]): boolean {
return a.length === b.length && a.every((value, index) => value === b[index]);
}
function indentContinuation(text: string, indent: string): string {
return text.split("\n").map((line, index) => index === 0 ? line : `${indent}${line}`).join("\n");
}
function uniqueSorted(values: string[]): string[] {
return [...new Set(values)].sort();
}
function redactUnknown(value: unknown): unknown {
if (typeof value === "string") return cleanText(value, false);
if (Array.isArray(value)) return value.map(item => redactUnknown(item));
if (!value || typeof value !== "object") return value;
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactUnknown(item)]));
}
+930
View File
@@ -0,0 +1,930 @@
import { createHash } from "node:crypto";
import type { EvidenceEventV1 } from "../../src/evidence-log.ts";
import { RETENTION_TYPE_MAX } from "../../src/retention.ts";
import type { LongTermMemoryEntry, LongTermType } from "../../src/types.ts";
import { TYPES } from "./constants.ts";
import { disappearanceRows } from "./inspection-model.ts";
import { rejectionQualitySummary, uniqueByCanonicalText } from "./rejections-model.ts";
import { canonicalMemoryText, cleanText, countBy, objectFromCounts, truncate, uniqueStrings, workspaceRootHash } from "./text.ts";
import type { MemoryInspectionReadModel, NormalizedRejection } from "./types.ts";
export type ReviewBoardReport = {
version: 1;
generatedAt: string;
workspace: { rootHash: string; key: string };
purpose: "review_evidence_only";
languageGuidance: {
nonAuthoritative: true;
mutation: "none";
rawReasonCodesAreEvidence: true;
producerVersionRecorded: false;
provenanceInferenceOnly: true;
primaryReviewPurpose: "system_mechanism_observations";
secondaryReviewPurpose: "memory_content_quality";
};
provenanceContext: {
method: "migration_timestamp_and_format_inference";
confidenceDisclaimer: string;
falseCurrentRiskBias: "prefer_unversioned_ambiguous_when_uncertain";
producerVersionAvailable: false;
migrationTimeline: Array<{ migrationId: string; presentInStore: boolean; firstEvidenceAt?: string }>;
lastActivityAt?: string;
countsByClassification: Record<ProvenanceClassification, number>;
candidateLimit?: number;
candidateDisplay?: {
shown: number;
total: number;
byMechanism: Record<string, { shown: number; total: number }>;
};
};
facts: {
systemMechanisms: {
rejectionFilters: {
totalRecords: number;
uniqueTexts: number;
byRawReasonCode: Record<string, number>;
byType: Record<string, number>;
ambiguousOrArchitectureLike: number;
hardReasonOrNoiseHeuristic: number;
reabsorbedRejectedTexts: number;
};
reinforcementRules: {
reinforceEvents: number;
reinforcedEvents: number;
rejectedOrBlockedEvents: number;
windowBlockedEvents: number;
windowBlockRate: number;
repeatedBlocksByMemory: Array<{ memoryId: string; count: number; refs: string[]; rawReasonCodes: string[] }>;
malformedCommandEvents: number;
};
evictionAndCaps: {
activeMemories: number;
maxEntries: number;
renderedMemories: number;
typeCounts: Record<string, number>;
typeCaps: Record<string, number>;
fullCaps: string[];
missingEvidenceOnly: number;
unknownDisappearances: number;
removedByCapacity: number;
removedByGlobalCap: number;
removedByTypeCap: number;
recentEvictionsByType: Record<string, number>;
recentEvictedContentShown: number;
};
identityAndDedup: {
replacementEvents: number;
sameTypeReplacementEvents: number;
crossTypeReplacementEvents: number;
supersededEntries: number;
duplicateTextOrIdentityGroups: number;
};
};
memoryContent: {
activeMemories: number;
renderedMemories: number;
evidenceCoverage: { covered: number; total: number };
typeCounts: Record<string, number>;
typeCaps: Record<string, number>;
weakestActiveMemories: Array<{ id: string; type: string; strength?: number; textPreview: string }>;
strongestActiveMemories: Array<{ id: string; type: string; strength?: number; textPreview: string }>;
};
};
activeMemoryDisplay: {
threshold: number;
mode: "all" | "sample";
shown: number;
total: number;
items: ReviewBoardActiveMemory[];
};
reviewCandidates: ReviewBoardCandidate[];
reviewQuestions: {
systemMechanism: string[];
memoryContent: string[];
};
nextCommands: string[];
};
export type ReviewBoardActiveMemory = {
id: string;
type: string;
source: string;
status: string;
strength?: number;
text: string;
evidence: { eventCount: number; eventIds: string[]; rawReasonCodes: string[] };
provenance?: CandidateProvenance;
heuristicFlags: HeuristicFlag[];
reviewQuestions: string[];
};
export type ReviewBoardCandidate = {
concernKind: "system_mechanism" | "memory_content";
mechanism?: "rejection_filter" | "reinforcement_rule" | "eviction_cap" | "identity_dedup" | "retention_rendering";
source:
| "active_memory"
| "rejection_rule_evidence"
| "missing_evidence"
| "numbered_command_evidence"
| "eviction_cap_evidence"
| "reabsorption_evidence"
| "identity_dedup_evidence";
id: string;
facts: Record<string, unknown>;
evidence: { eventIds?: string[]; rawReasonCodes?: string[]; textPreview?: string; textAvailable: boolean };
provenance?: CandidateProvenance;
heuristicFlags: HeuristicFlag[];
reviewQuestions: string[];
nextCommands: string[];
};
export type ProvenanceClassification =
| "explicit_migration_evidence"
| "legacy_unversioned_format"
| "reabsorbed_post_rejection"
| "suspected_pre_migration_legacy"
| "likely_current_behavior"
| "unversioned_ambiguous";
export type CandidateProvenance = {
classification: ProvenanceClassification;
confidence: "high" | "medium" | "low";
basis: string[];
interpretationCaveat: string;
};
export type HeuristicFlag = {
id: string;
label: string;
evidence: string;
caveat: string;
};
export const ACTIVE_MEMORY_FULL_TEXT_THRESHOLD = 40;
export const REPRESENTATIVE_CANDIDATE_LIMIT = 10;
export const RECENT_EVICTION_DAYS = 7;
const KNOWN_MIGRATION_IDS = [
"2026-04-26-p0-cleanup",
"2026-04-28-quality-cleanup",
"2026-05-01-retention-clock-backfill",
] as const;
const PROVENANCE_CLASSIFICATIONS: ProvenanceClassification[] = [
"explicit_migration_evidence",
"legacy_unversioned_format",
"reabsorbed_post_rejection",
"suspected_pre_migration_legacy",
"likely_current_behavior",
"unversioned_ambiguous",
];
const HARD_OR_NOISE_REASON_CODES = new Set([
"progress_snapshot",
"active_file_snapshot",
"commit_or_ci_snapshot",
"temporary_status",
"raw_error",
"code_or_api_signature",
"bad_feedback",
]);
const MALFORMED_COMMAND_REASON_CODES = new Set([
"invalid_memory_command",
"invalid_memory_ref",
"invalid_memory_type",
"empty_replacement_text",
]);
type ReabsorbedMatch = {
key: string;
record: NormalizedRejection;
activeMemory: LongTermMemoryEntry;
};
type ProvenanceContextInputs = {
firstMigrationBoundary?: string;
latestMigrationBoundary?: string;
lastActivityAt?: string;
};
type DatedCandidateInput = {
candidate: ReviewBoardCandidate;
timestamp?: string;
tieId: string;
textHash?: string;
};
export function buildQualityReviewBoard(
model: MemoryInspectionReadModel,
options: { verbose?: boolean; raw?: boolean; noEmoji?: boolean; json?: boolean },
generatedAt = new Date().toISOString(),
): ReviewBoardReport {
const raw = options.raw === true;
const activeMemories = model.store.entries.filter(entry => entry.status !== "superseded");
const typeCounts = typeCountsFor(activeMemories);
const typeCaps = Object.fromEntries(TYPES.map(type => [type, RETENTION_TYPE_MAX[type]]));
const rejectionSummary = rejectionQualitySummary(model.rejectionRecords);
const rejectionGroups = rejectionSummary.possibleFalsePositiveGroups;
const migrationTimeline = buildMigrationTimeline(model);
const provenanceInputs = migrationBoundaries(model);
const activeKeyMatches = reabsorbedRejectedMatches(activeMemories, model.rejectionRecords);
const reabsorbedKeys = new Set(activeKeyMatches.map(match => match.key));
const activeMemoryByKey = new Map(activeKeyMatches.map(match => [match.key, match.activeMemory]));
const disappearances = disappearanceRows(model);
const reinforcementFacts = buildReinforcementFacts(model.evidenceEvents);
const evictionFacts = buildEvictionFacts(model, activeMemories, typeCounts, typeCaps, disappearances, generatedAt);
const identityFacts = buildIdentityFacts(model, activeMemories);
const memoryContentFacts = buildMemoryContentFacts(model, activeMemories, typeCounts, typeCaps, raw);
const systemMechanismCandidateInputs = {
rejection_filter: [
...buildRejectionCandidates(model.rejectionRecords, provenanceInputs, raw),
...buildReabsorptionCandidates(activeKeyMatches, provenanceInputs, raw),
],
reinforcement_rule: buildReinforcementCandidates(model.evidenceEvents, provenanceInputs, raw),
eviction_cap: buildEvictionCandidates(disappearances, model.evidenceEvents, provenanceInputs, raw, generatedAt),
identity_dedup: buildIdentityCandidates(model, activeMemories, provenanceInputs, raw),
};
const showAllSystemMechanismCandidates = options.verbose === true || options.json === true;
const systemCandidateDisplay = buildSystemCandidateDisplay(systemMechanismCandidateInputs, showAllSystemMechanismCandidates);
const allSystemMechanismCandidates = Object.values(systemMechanismCandidateInputs)
.flatMap(inputs => selectRepresentative(inputs, true).map(item => item.candidate));
const reviewCandidates = [
...systemCandidateDisplay.candidates,
...buildMemoryContentCandidates(model, activeMemories, raw),
];
const activeMemoryDisplay = buildActiveMemoryDisplay(model, activeMemories, reabsorbedKeys, activeMemoryByKey, provenanceInputs, raw, options.verbose === true);
const countsByClassification = countProvenanceClassifications(allSystemMechanismCandidates);
return {
version: 1,
generatedAt,
workspace: {
rootHash: workspaceRootHash(model.snapshot?.store?.workspace?.root ?? model.store.workspace.root),
key: model.snapshot?.store?.workspace?.key ?? model.store.workspace.key,
},
purpose: "review_evidence_only",
languageGuidance: {
nonAuthoritative: true,
mutation: "none",
rawReasonCodesAreEvidence: true,
producerVersionRecorded: false,
provenanceInferenceOnly: true,
primaryReviewPurpose: "system_mechanism_observations",
secondaryReviewPurpose: "memory_content_quality",
},
provenanceContext: {
method: "migration_timestamp_and_format_inference",
confidenceDisclaimer: "Producer version is not recorded in historical evidence; provenance is inferred and should not be used as proof of current behavior.",
falseCurrentRiskBias: "prefer_unversioned_ambiguous_when_uncertain",
producerVersionAvailable: false,
migrationTimeline,
lastActivityAt: model.store.lastActivityAt,
countsByClassification,
...(systemCandidateDisplay.limited ? { candidateLimit: REPRESENTATIVE_CANDIDATE_LIMIT, candidateDisplay: systemCandidateDisplay.summary } : {}),
},
facts: {
systemMechanisms: {
rejectionFilters: {
totalRecords: rejectionSummary.totalRecords,
uniqueTexts: rejectionSummary.uniqueTexts,
byRawReasonCode: rejectionSummary.reasonDistribution,
byType: objectFromCounts(countBy(model.rejectionRecords.map(record => record.type))),
ambiguousOrArchitectureLike:
(rejectionGroups.architecture_like_possible_false_positive?.count ?? 0)
+ (rejectionGroups.ambiguous?.count ?? 0),
hardReasonOrNoiseHeuristic: rejectionGroups.clearly_garbage?.count ?? 0,
reabsorbedRejectedTexts: new Set(activeKeyMatches.map(match => match.key)).size,
},
reinforcementRules: reinforcementFacts,
evictionAndCaps: evictionFacts,
identityAndDedup: identityFacts,
},
memoryContent: memoryContentFacts,
},
activeMemoryDisplay,
reviewCandidates,
reviewQuestions: {
systemMechanism: systemMechanismQuestions(),
memoryContent: memoryContentQuestions(),
},
nextCommands: nextCommands(),
};
}
function typeCountsFor(entries: LongTermMemoryEntry[]): Record<string, number> {
return Object.fromEntries(TYPES.map(type => [type, entries.filter(entry => entry.type === type).length]));
}
function buildMigrationTimeline(model: MemoryInspectionReadModel): ReviewBoardReport["provenanceContext"]["migrationTimeline"] {
const present = new Set(model.store.migrations ?? []);
return KNOWN_MIGRATION_IDS.map(migrationId => {
const matchingTimes = model.evidenceEvents
.filter(event => event.details?.migrationId === migrationId)
.map(event => event.createdAt)
.sort();
const row: { migrationId: string; presentInStore: boolean; firstEvidenceAt?: string } = {
migrationId,
presentInStore: present.has(migrationId),
};
if (matchingTimes[0]) row.firstEvidenceAt = matchingTimes[0];
return row;
});
}
function migrationBoundaries(model: MemoryInspectionReadModel): ProvenanceContextInputs {
const present = new Set(model.store.migrations ?? []);
const matchingTimes = model.evidenceEvents
.filter(event => typeof event.details?.migrationId === "string" && present.has(event.details.migrationId))
.map(event => event.createdAt)
.sort();
return {
firstMigrationBoundary: matchingTimes[0],
latestMigrationBoundary: matchingTimes[matchingTimes.length - 1],
lastActivityAt: model.store.lastActivityAt,
};
}
function classifyProvenance(input: {
event?: EvidenceEventV1;
rejection?: NormalizedRejection;
reabsorbed?: boolean;
}, context: ProvenanceContextInputs): CandidateProvenance {
if (input.event?.details?.migrationId) {
return provenance("explicit_migration_evidence", "high", [`migration evidence event ${String(input.event.details.migrationId)}`]);
}
if (input.rejection && !hasWorkspaceScope(input.rejection)) {
return provenance("legacy_unversioned_format", "high", ["rejection record without workspace scope fields"]);
}
if (input.reabsorbed) {
return provenance("reabsorbed_post_rejection", "high", ["typed canonical rejected text appears in active memory"]);
}
const timestamp = input.event?.createdAt ?? input.rejection?.timestamp;
if (timestamp && context.firstMigrationBoundary && compareIso(timestamp, context.firstMigrationBoundary) < 0) {
return provenance("suspected_pre_migration_legacy", "medium", ["evidence timestamp predates first known migration boundary"]);
}
if (timestamp && context.latestMigrationBoundary && compareIso(timestamp, context.latestMigrationBoundary) >= 0) {
if (!context.lastActivityAt || compareIso(timestamp, context.lastActivityAt) >= 0) {
return provenance("likely_current_behavior", "medium", ["evidence timestamp is after known migration evidence and workspace last activity"]);
}
}
return provenance("unversioned_ambiguous", "low", ["no producer version or decisive migration/timestamp signal is recorded"]);
}
function provenance(classification: ProvenanceClassification, confidence: CandidateProvenance["confidence"], basis: string[]): CandidateProvenance {
return {
classification,
confidence,
basis,
interpretationCaveat: "Producer version is not recorded; treat this as inferred review context rather than proof of current behavior.",
};
}
function hasWorkspaceScope(record: NormalizedRejection): boolean {
return Boolean(record.workspaceKey || record.workspaceRoot || record.workspaceRootHash);
}
function compareIso(a: string, b: string): number {
const aTime = new Date(a).getTime();
const bTime = new Date(b).getTime();
if (!Number.isFinite(aTime) || !Number.isFinite(bTime)) return 0;
return aTime - bTime;
}
function reabsorbedRejectedMatches(activeMemories: LongTermMemoryEntry[], records: NormalizedRejection[]): ReabsorbedMatch[] {
const activeByKey = new Map(activeMemories.map(memory => [typedCanonicalKey(memory.type, memory.text), memory]));
const matches: ReabsorbedMatch[] = [];
const seen = new Set<string>();
for (const record of records) {
const key = typedCanonicalKey(record.type, record.text);
const activeMemory = activeByKey.get(key);
if (!activeMemory || seen.has(key)) continue;
seen.add(key);
matches.push({ key, record, activeMemory });
}
return matches;
}
function typedCanonicalKey(type: LongTermType | string, text: string): string {
return `${type}:${canonicalMemoryText(text)}`;
}
function buildReinforcementFacts(events: EvidenceEventV1[]): ReviewBoardReport["facts"]["systemMechanisms"]["reinforcementRules"] {
const attempts = events.filter(isReinforcementEvent);
const windowBlocked = attempts.filter(event => event.reasonCodes.includes("reinforcement_window_blocked"));
const grouped = new Map<string, { memoryId: string; count: number; refs: Set<string>; rawReasonCodes: Set<string>; eventIds: string[] }>();
for (const event of windowBlocked) {
const memoryId = event.memory?.memoryId ?? "unknown";
const current = grouped.get(memoryId) ?? { memoryId, count: 0, refs: new Set<string>(), rawReasonCodes: new Set<string>(), eventIds: [] };
current.count += 1;
current.eventIds.push(event.eventId);
const ref = typeof event.details?.ref === "string" ? event.details.ref : undefined;
if (ref) current.refs.add(ref);
for (const reason of event.reasonCodes) current.rawReasonCodes.add(reason);
grouped.set(memoryId, current);
}
return {
reinforceEvents: attempts.length,
reinforcedEvents: attempts.filter(event => event.outcome === "reinforced" || event.type === "memory_reinforced" && event.outcome !== "rejected").length,
rejectedOrBlockedEvents: attempts.filter(event => event.outcome === "rejected" || event.reasonCodes.includes("reinforcement_window_blocked")).length,
windowBlockedEvents: windowBlocked.length,
windowBlockRate: attempts.length === 0 ? 0 : windowBlocked.length / attempts.length,
repeatedBlocksByMemory: [...grouped.values()]
.filter(group => group.count > 1)
.sort((a, b) => b.count - a.count || a.memoryId.localeCompare(b.memoryId))
.map(group => ({ memoryId: group.memoryId, count: group.count, refs: [...group.refs].sort(), rawReasonCodes: [...group.rawReasonCodes].sort() })),
malformedCommandEvents: events.filter(isMalformedCommandEvent).length,
};
}
function isReinforcementEvent(event: EvidenceEventV1): boolean {
const type = String(event.type);
return type === "memory_reinforced"
|| type === "reinforce_memory"
|| type === "reinforced"
|| event.phase === "reinforcement";
}
function isMalformedCommandEvent(event: EvidenceEventV1): boolean {
return event.type === "extraction_candidate_rejected"
&& event.reasonCodes.some(reason => MALFORMED_COMMAND_REASON_CODES.has(reason));
}
function buildEvictionFacts(
model: MemoryInspectionReadModel,
activeMemories: LongTermMemoryEntry[],
typeCounts: Record<string, number>,
typeCaps: Record<string, number>,
disappearances: ReturnType<typeof disappearanceRows>,
generatedAt: string,
): ReviewBoardReport["facts"]["systemMechanisms"]["evictionAndCaps"] {
const capacityEvents = model.evidenceEvents.filter(event => event.type === "memory_removed_capacity");
const recentCapacityEvents = capacityEvents.filter(event => isWithinDaysOf(event.createdAt, generatedAt, RECENT_EVICTION_DAYS));
const fullCaps = [
...(activeMemories.length >= model.store.limits.maxEntries ? ["global"] : []),
...TYPES.filter(type => (typeCounts[type] ?? 0) >= (typeCaps[type] ?? Number.POSITIVE_INFINITY)),
];
return {
activeMemories: activeMemories.length,
maxEntries: model.store.limits.maxEntries,
renderedMemories: model.snapshot.retention.rendered.length,
typeCounts,
typeCaps,
fullCaps,
missingEvidenceOnly: disappearances.length,
unknownDisappearances: disappearances.filter(row => row.classification === "historical_absent_unknown_reason").length,
removedByCapacity: capacityEvents.length,
removedByGlobalCap: capacityEvents.filter(event => event.reasonCodes.includes("global_cap")).length,
removedByTypeCap: capacityEvents.filter(event => event.reasonCodes.includes("type_cap")).length,
recentEvictionsByType: objectFromCounts(countBy(recentCapacityEvents.map(event => event.memory?.type ?? "unknown"))),
recentEvictedContentShown: recentCapacityEvents.length,
};
}
function isWithinDaysOf(iso: string, referenceIso: string, days: number): boolean {
const time = new Date(iso).getTime();
const reference = new Date(referenceIso).getTime();
return Number.isFinite(time) && Number.isFinite(reference) && time >= reference - days * 86_400_000;
}
function buildIdentityFacts(model: MemoryInspectionReadModel, activeMemories: LongTermMemoryEntry[]): ReviewBoardReport["facts"]["systemMechanisms"]["identityAndDedup"] {
const replacementEvents = model.evidenceEvents.filter(event => event.type === "memory_replaced_numbered_ref");
return {
replacementEvents: replacementEvents.length,
sameTypeReplacementEvents: replacementEvents.filter(isSameTypeReplacement).length,
crossTypeReplacementEvents: replacementEvents.filter(isCrossTypeReplacement).length,
supersededEntries: model.store.entries.filter(entry => entry.status === "superseded").length,
duplicateTextOrIdentityGroups: duplicateGroups(activeMemories, model.evidenceEvents).length,
};
}
function isSameTypeReplacement(event: EvidenceEventV1): boolean {
if (event.reasonCodes.includes("same_type_replace")) return true;
const types = relationTypes(event);
return types.length >= 2 && new Set(types).size === 1;
}
function isCrossTypeReplacement(event: EvidenceEventV1): boolean {
if (event.reasonCodes.includes("cross_type_replace")) return true;
const types = relationTypes(event);
return types.length >= 2 && new Set(types).size > 1;
}
function relationTypes(event: EvidenceEventV1): string[] {
return uniqueStrings(event.relations?.map(relation => relation.memory?.type ?? "") ?? []);
}
function duplicateGroups(activeMemories: LongTermMemoryEntry[], events: EvidenceEventV1[]): Array<{ id: string; memoryIds: string[]; basis: string }> {
const groups: Array<{ id: string; memoryIds: string[]; basis: string }> = [];
const byText = groupBy(activeMemories, memory => typedCanonicalKey(memory.type, memory.text));
for (const [key, memories] of byText.entries()) {
if (memories.length > 1) groups.push({ id: `text:${hashText(key)}`, memoryIds: memories.map(memory => memory.id).sort(), basis: "exact typed canonical text" });
}
const identityRefs = events
.map(event => event.memory)
.filter((memory): memory is NonNullable<EvidenceEventV1["memory"]> => Boolean(memory?.identityKeyHash && memory.memoryId));
const byIdentity = groupBy(identityRefs, memory => String(memory.identityKeyHash));
for (const [key, refs] of byIdentity.entries()) {
const ids = uniqueStrings(refs.map(ref => ref.memoryId ?? "")).sort();
if (ids.length > 1) groups.push({ id: `identity:${hashText(key)}`, memoryIds: ids, basis: "shared evidence identity hash" });
}
return groups;
}
function groupBy<T>(items: T[], keyFor: (item: T) => string): Map<string, T[]> {
const grouped = new Map<string, T[]>();
for (const item of items) {
const key = keyFor(item);
grouped.set(key, [...(grouped.get(key) ?? []), item]);
}
return grouped;
}
function buildMemoryContentFacts(
model: MemoryInspectionReadModel,
activeMemories: LongTermMemoryEntry[],
typeCounts: Record<string, number>,
typeCaps: Record<string, number>,
raw: boolean,
): ReviewBoardReport["facts"]["memoryContent"] {
const evidenceCovered = activeMemories.filter(memory => (model.evidenceByMemoryId.get(memory.id) ?? []).length > 0).length;
const weakest = model.snapshot.retention.sorted.slice(-5).reverse();
const strongest = model.snapshot.retention.sorted.slice(0, 5);
return {
activeMemories: activeMemories.length,
renderedMemories: model.snapshot.retention.rendered.length,
evidenceCoverage: { covered: evidenceCovered, total: activeMemories.length },
typeCounts,
typeCaps,
weakestActiveMemories: weakest.map(item => retentionPreview(item.entry, item.strength, raw)),
strongestActiveMemories: strongest.map(item => retentionPreview(item.entry, item.strength, raw)),
};
}
function retentionPreview(entry: LongTermMemoryEntry, strength: number | undefined, raw: boolean): { id: string; type: string; strength?: number; textPreview: string } {
return { id: entry.id, type: entry.type, strength, textPreview: truncate(cleanText(entry.text, raw), 120) };
}
function buildActiveMemoryDisplay(
model: MemoryInspectionReadModel,
activeMemories: LongTermMemoryEntry[],
reabsorbedKeys: Set<string>,
activeMemoryByKey: Map<string, LongTermMemoryEntry>,
provenanceInputs: ProvenanceContextInputs,
raw: boolean,
verbose: boolean,
): ReviewBoardReport["activeMemoryDisplay"] {
const mode: "all" | "sample" = activeMemories.length <= ACTIVE_MEMORY_FULL_TEXT_THRESHOLD || verbose ? "all" : "sample";
const shownMemories = mode === "all" ? activeMemories : activeMemories.slice(0, ACTIVE_MEMORY_FULL_TEXT_THRESHOLD);
const items = shownMemories.map(memory => {
const events = model.evidenceByMemoryId.get(memory.id) ?? [];
const key = typedCanonicalKey(memory.type, memory.text);
const item: ReviewBoardActiveMemory = {
id: memory.id,
type: memory.type,
source: memory.source,
status: memory.status,
strength: model.snapshot.retention.sorted.find(candidate => candidate.entry.id === memory.id)?.strength,
text: cleanText(memory.text, raw),
evidence: {
eventCount: events.length,
eventIds: events.map(event => event.eventId),
rawReasonCodes: uniqueStrings(events.flatMap(event => event.reasonCodes)).sort(),
},
heuristicFlags: activeMemoryFlags(memory, events),
reviewQuestions: memoryContentQuestions(),
};
if (reabsorbedKeys.has(key) && activeMemoryByKey.get(key)?.id === memory.id) {
item.provenance = classifyProvenance({ reabsorbed: true }, provenanceInputs);
}
return item;
});
return { threshold: ACTIVE_MEMORY_FULL_TEXT_THRESHOLD, mode, shown: items.length, total: activeMemories.length, items };
}
function activeMemoryFlags(memory: LongTermMemoryEntry, events: EvidenceEventV1[]): HeuristicFlag[] {
const flags: HeuristicFlag[] = [];
if (events.length === 0) {
flags.push(flag("no_evidence", "No linked evidence events", `memory ${memory.id} has no lifecycle evidence events`));
}
if ((memory.supersedes ?? []).length > 0) {
flags.push(flag("supersedes_other_memory", "Supersession relationship present", `memory ${memory.id} supersedes ${memory.supersedes?.length ?? 0} prior entries`));
}
return flags;
}
function buildSystemCandidateDisplay(
candidateInputs: Record<string, DatedCandidateInput[]>,
showAll: boolean,
): { candidates: ReviewBoardCandidate[]; limited: boolean; summary: NonNullable<ReviewBoardReport["provenanceContext"]["candidateDisplay"]> } {
const candidates: ReviewBoardCandidate[] = [];
const byMechanism: Record<string, { shown: number; total: number }> = {};
let shown = 0;
let total = 0;
let limited = false;
for (const [mechanism, inputs] of Object.entries(candidateInputs)) {
const selected = selectRepresentative(inputs, showAll);
candidates.push(...selected.map(item => item.candidate));
byMechanism[mechanism] = { shown: selected.length, total: inputs.length };
shown += selected.length;
total += inputs.length;
if (selected.length < inputs.length) limited = true;
}
return { candidates, limited, summary: { shown, total, byMechanism } };
}
function buildRejectionCandidates(records: NormalizedRejection[], context: ProvenanceContextInputs, raw: boolean): DatedCandidateInput[] {
const candidateRecords = records
.filter(record => record.reasons.includes("bad_decision"))
.map(record => ({ record, label: neutralRejectionLabel(record) }))
.filter(item => item.label === "architecture_like_rejected_candidate" || item.label === "ambiguous_rejected_candidate")
.sort((a, b) =>
timestampValue(b.record.timestamp) - timestampValue(a.record.timestamp)
|| a.record.type.localeCompare(b.record.type)
|| a.record.text.localeCompare(b.record.text)
);
const candidates = uniqueByCanonicalText(candidateRecords.map(item => item.record))
.map(record => ({ record, label: neutralRejectionLabel(record) }))
.map(({ record, label }) => ({
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "rejection_filter",
source: "rejection_rule_evidence",
id: `rejection:${record.timestamp || "unknown"}:${hashText(record.type + record.text)}`,
facts: { type: record.type, neutralLabel: label, timestamp: record.timestamp || undefined, origin: record.origin },
evidence: { rawReasonCodes: record.reasons, textPreview: truncate(cleanText(record.text, raw), 120), textAvailable: true },
provenance: classifyProvenance({ rejection: record }, context),
heuristicFlags: [flag(label, label.replaceAll("_", " "), "existing rejection summary grouped this record for human review")],
reviewQuestions: ["Are rejection filters over-filtering durable decisions or under-filtering non-durable candidates for this workspace?"],
nextCommands: ["memory-diag rejected --verbose"],
}),
timestamp: record.timestamp,
tieId: record.timestamp || "unknown",
textHash: hashText(record.text),
}));
return candidates;
}
function neutralRejectionLabel(record: NormalizedRejection): "architecture_like_rejected_candidate" | "ambiguous_rejected_candidate" | "status_or_hard_reason_evidence" {
const hardReasons = record.reasons.filter(reason => HARD_OR_NOISE_REASON_CODES.has(reason));
const statusLike = /\b(?:implemented|added|updated|fixed|completed|reviewed|tests?|CI|commit|wave|phase|task|session)\b/i.test(record.text);
const architectureLike = /\b(?:architecture|retention|migration|schema|policy|model|dedup|identity|parser|formatter|diagnostic|evidence|cap|window|api|contract)\b/i.test(record.text);
if (architectureLike && hardReasons.length === 0 && !statusLike) return "architecture_like_rejected_candidate";
if (hardReasons.length > 0 || statusLike) return "status_or_hard_reason_evidence";
return "ambiguous_rejected_candidate";
}
function buildReabsorptionCandidates(matches: ReabsorbedMatch[], context: ProvenanceContextInputs, raw: boolean): DatedCandidateInput[] {
const candidates = matches.map(match => ({
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "rejection_filter",
source: "reabsorption_evidence",
id: `reabsorbed:${match.activeMemory.id}:${hashText(match.key)}`,
facts: { activeMemoryId: match.activeMemory.id, type: match.activeMemory.type, rejectedAt: match.record.timestamp || undefined },
evidence: { rawReasonCodes: match.record.reasons, textPreview: truncate(cleanText(match.record.text, raw), 120), textAvailable: true },
provenance: classifyProvenance({ rejection: match.record, reabsorbed: true }, context),
heuristicFlags: [flag("reabsorbed_rejected_text", "Rejected text appears in active memory", "typed canonical text is present in both rejection records and active memory")],
reviewQuestions: ["Did later context make this rejected candidate worth reviewing for filter calibration?"],
nextCommands: ["memory-diag rejected --verbose", `memory-diag explain ${match.activeMemory.id}`],
}),
timestamp: match.record.timestamp,
tieId: match.activeMemory.id,
textHash: hashText(match.key),
}));
return candidates;
}
function buildReinforcementCandidates(events: EvidenceEventV1[], context: ProvenanceContextInputs, raw: boolean): DatedCandidateInput[] {
const blocked = events.filter(event => isReinforcementEvent(event) && event.reasonCodes.includes("reinforcement_window_blocked"));
const grouped = [...groupBy(blocked, event => event.memory?.memoryId ?? "unknown").entries()].map(([memoryId, group]) => ({ memoryId, group }));
const repeated = grouped.filter(item => item.group.length > 1).map(item => {
const latest = newestEvent(item.group);
return {
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "reinforcement_rule",
source: "numbered_command_evidence",
id: `reinforcement:${item.memoryId}:${item.group.length}`,
facts: { memoryId: item.memoryId, blockCount: item.group.length, refs: uniqueStrings(item.group.map(event => String(event.details?.ref ?? "")).filter(Boolean)).sort() },
evidence: { eventIds: item.group.map(event => event.eventId), rawReasonCodes: uniqueStrings(item.group.flatMap(event => event.reasonCodes)).sort(), textAvailable: false },
provenance: classifyProvenance({ event: latest }, context),
heuristicFlags: [flag("repeated_reinforcement_window_block", "Repeated reinforcement window block", `${item.group.length} reinforcement attempts were blocked for memory ${item.memoryId}`)],
reviewQuestions: ["Is the day-based reinforcement window too restrictive when the same memory receives repeated reinforce intent?"],
nextCommands: ["memory-diag commands --verbose", `memory-diag explain ${item.memoryId}`],
}),
timestamp: latest?.createdAt,
tieId: item.memoryId,
textHash: item.memoryId,
};
});
const malformed = events.filter(isMalformedCommandEvent).map(event => ({
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "reinforcement_rule",
source: "numbered_command_evidence",
id: `malformed-command:${event.eventId}`,
facts: { eventType: event.type, createdAt: event.createdAt },
evidence: { eventIds: [event.eventId], rawReasonCodes: event.reasonCodes, textPreview: event.textPreview ? truncate(cleanText(event.textPreview, raw), 120) : undefined, textAvailable: Boolean(event.textPreview) },
provenance: classifyProvenance({ event }, context),
heuristicFlags: [flag("malformed_numbered_command", "Malformed numbered-memory command evidence", "command parser rejected a memory command form")],
reviewQuestions: ["Do numbered-memory command rules match how agents actually express reinforcement intent?"],
nextCommands: ["memory-diag commands --verbose"],
}),
timestamp: event.createdAt,
tieId: event.eventId,
textHash: event.eventId,
}));
return [...repeated, ...malformed];
}
function buildEvictionCandidates(
disappearances: ReturnType<typeof disappearanceRows>,
events: EvidenceEventV1[],
context: ProvenanceContextInputs,
raw: boolean,
generatedAt: string,
): DatedCandidateInput[] {
const recentCapacity = events.filter(event => event.type === "memory_removed_capacity" && isWithinDaysOf(event.createdAt, generatedAt, RECENT_EVICTION_DAYS));
const capacityCandidates = recentCapacity.map(event => ({
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "eviction_cap",
source: "eviction_cap_evidence",
id: `eviction:${event.eventId}`,
facts: { ...(safeDetails(event.details, raw) ?? {}), createdAt: event.createdAt, memoryId: event.memory?.memoryId, type: event.memory?.type },
evidence: { eventIds: [event.eventId], rawReasonCodes: event.reasonCodes, textPreview: event.textPreview ? truncate(cleanText(event.textPreview, raw), 120) : undefined, textAvailable: Boolean(event.textPreview) },
provenance: classifyProvenance({ event }, context),
heuristicFlags: [flag("recent_capacity_removal", "Recent capacity-removal evidence", "memory_removed_capacity appeared within the recent eviction window")],
reviewQuestions: ["Are eviction and cap rules preserving the intended memories under pressure?"],
nextCommands: ["memory-diag missing --verbose --explain"],
}),
timestamp: event.createdAt,
tieId: event.eventId,
textHash: event.textPreview ? hashText(event.textPreview) : event.eventId,
}));
const unknownCandidates = disappearances
.filter(row => row.classification === "historical_absent_unknown_reason")
.map(row => {
const latest = newestEvent(row.events);
return {
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "eviction_cap",
source: "missing_evidence",
id: `missing:${row.id}`,
facts: { memoryId: row.id, terminalType: row.terminalType, eventCount: row.events.length },
evidence: { eventIds: row.events.map(event => event.eventId), rawReasonCodes: uniqueStrings(row.events.flatMap(event => event.reasonCodes)).sort(), textPreview: latest?.textPreview ? truncate(cleanText(latest.textPreview, raw), 120) : undefined, textAvailable: Boolean(latest?.textPreview) },
provenance: classifyProvenance({ event: latest }, context),
heuristicFlags: [flag("unknown_disappearance", "Evidence-only disappearance without terminal removal evidence", `memory ${row.id} has evidence but is not active`)],
reviewQuestions: ["Does missing-memory evidence indicate a cap, retention, or recording rule needs review?"],
nextCommands: ["memory-diag missing --verbose --explain"],
}),
timestamp: latest?.createdAt,
tieId: row.id,
textHash: row.id,
};
});
return [...capacityCandidates, ...unknownCandidates];
}
function safeDetails(details: EvidenceEventV1["details"], raw: boolean): Record<string, unknown> | undefined {
if (!details) return undefined;
return Object.fromEntries(Object.entries(details).map(([key, value]) => [key, typeof value === "string" ? cleanText(value, raw) : value]));
}
function buildIdentityCandidates(model: MemoryInspectionReadModel, activeMemories: LongTermMemoryEntry[], context: ProvenanceContextInputs, raw: boolean): DatedCandidateInput[] {
const replacementCandidates = model.evidenceEvents
.filter(event => event.type === "memory_replaced_numbered_ref" || event.type === "promotion_superseded")
.map(event => ({
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "identity_dedup",
source: "identity_dedup_evidence",
id: `identity-event:${event.eventId}`,
facts: { eventType: event.type, memoryId: event.memory?.memoryId, relationRoles: event.relations?.map(relation => relation.role) ?? [] },
evidence: { eventIds: [event.eventId], rawReasonCodes: event.reasonCodes, textPreview: event.textPreview ? truncate(cleanText(event.textPreview, raw), 120) : undefined, textAvailable: Boolean(event.textPreview) },
provenance: classifyProvenance({ event }, context),
heuristicFlags: [flag("replacement_or_supersession", "Replacement or supersession evidence", `${event.type} records identity/dedup behavior`)],
reviewQuestions: ["Are identity and dedup rules preserving separate memories when expected to remain distinct?"],
nextCommands: ["memory-diag commands --verbose", event.memory?.memoryId ? `memory-diag explain ${event.memory.memoryId}` : "memory-diag missing --verbose --explain"],
}),
timestamp: event.createdAt,
tieId: event.eventId,
textHash: event.eventId,
}));
const duplicateCandidates = duplicateGroups(activeMemories, model.evidenceEvents).map(group => ({
candidate: candidate({
concernKind: "system_mechanism",
mechanism: "identity_dedup",
source: "identity_dedup_evidence",
id: `duplicate:${group.id}`,
facts: { memoryIds: group.memoryIds, basis: group.basis },
evidence: { eventIds: group.memoryIds.flatMap(id => (model.evidenceByMemoryId.get(id) ?? []).map(event => event.eventId)), rawReasonCodes: [], textAvailable: false },
provenance: classifyProvenance({}, context),
heuristicFlags: [flag("exact_duplicate_group", "Exact duplicate text or identity group", `${group.memoryIds.length} memories share ${group.basis}`)],
reviewQuestions: ["Are exact duplicate text or identity groups expected for this workspace?"],
nextCommands: group.memoryIds.map(id => `memory-diag explain ${id}`).slice(0, 3),
}),
timestamp: undefined,
tieId: group.id,
textHash: group.id,
}));
return [...replacementCandidates, ...duplicateCandidates];
}
function buildMemoryContentCandidates(model: MemoryInspectionReadModel, activeMemories: LongTermMemoryEntry[], raw: boolean): ReviewBoardCandidate[] {
return activeMemories.slice(0, ACTIVE_MEMORY_FULL_TEXT_THRESHOLD).map(memory => {
const events = model.evidenceByMemoryId.get(memory.id) ?? [];
return candidate({
concernKind: "memory_content",
mechanism: "retention_rendering",
source: "active_memory",
id: `active:${memory.id}`,
facts: { id: memory.id, type: memory.type, source: memory.source, status: memory.status },
evidence: { eventIds: events.map(event => event.eventId), rawReasonCodes: uniqueStrings(events.flatMap(event => event.reasonCodes)).sort(), textPreview: truncate(cleanText(memory.text, raw), 120), textAvailable: true },
heuristicFlags: activeMemoryFlags(memory, events),
reviewQuestions: memoryContentQuestions(),
nextCommands: [`memory-diag explain ${memory.id}`],
});
});
}
function candidate(input: ReviewBoardCandidate): ReviewBoardCandidate {
return input;
}
function selectRepresentative(items: DatedCandidateInput[], verbose: boolean): DatedCandidateInput[] {
const sorted = [...items].sort((a, b) => {
const timeDelta = timestampValue(b.timestamp) - timestampValue(a.timestamp);
if (timeDelta !== 0) return timeDelta;
const idDelta = a.tieId.localeCompare(b.tieId);
if (idDelta !== 0) return idDelta;
return (a.textHash ?? "").localeCompare(b.textHash ?? "");
});
return verbose ? sorted : sorted.slice(0, REPRESENTATIVE_CANDIDATE_LIMIT);
}
function timestampValue(iso: string | undefined): number {
const time = iso ? new Date(iso).getTime() : 0;
return Number.isFinite(time) ? time : 0;
}
function newestEvent(events: EvidenceEventV1[]): EvidenceEventV1 | undefined {
return [...events].sort((a, b) => timestampValue(b.createdAt) - timestampValue(a.createdAt) || a.eventId.localeCompare(b.eventId))[0];
}
function flag(id: string, label: string, evidence: string): HeuristicFlag {
return {
id,
label,
evidence,
caveat: "This flag is a prompt for review, not a conclusion.",
};
}
function systemMechanismQuestions(): string[] {
return [
"Are rejection rules over-filtering durable decisions or under-filtering non-durable candidates for this workspace?",
"Is the reinforcement window too restrictive when the same memory receives repeated reinforce intent?",
"Are eviction and cap rules preserving target memories under full caps?",
"Are identity and dedup rules collapsing items expected to remain separate, or not collapsing equivalent items?",
];
}
function memoryContentQuestions(): string[] {
return [
"Does this memory remain durable and actionable for future sessions?",
"Is this memory non-stale, specific, and supported by available evidence?",
"Does this memory overlap with other active memories in a way a reviewer should consider?",
];
}
function nextCommands(): string[] {
return [
"memory-diag rejected --verbose",
"memory-diag missing --verbose --explain",
"memory-diag commands --verbose",
"memory-diag explain <memory-id>",
];
}
function countProvenanceClassifications(candidates: ReviewBoardCandidate[]): Record<ProvenanceClassification, number> {
const counts = Object.fromEntries(PROVENANCE_CLASSIFICATIONS.map(classification => [classification, 0])) as Record<ProvenanceClassification, number>;
for (const provenanceItem of candidates.map(candidate => candidate.provenance)) {
if (!provenanceItem) continue;
counts[provenanceItem.classification] += 1;
}
return counts;
}
function hashText(text: string): string {
return createHash("sha256").update(text).digest("hex").slice(0, 12);
}
+38 -2
View File
@@ -9,9 +9,10 @@ test("help returns usage without exposing hidden or removed commands", () => {
assert.equal("help" in parsed && parsed.help, true);
assert.match(parsed.usage, /Usage:/);
assert.match(parsed.usage, /memory-diag \[status\]/);
assert.match(parsed.usage, /memory-diag quality/);
assert.match(parsed.usage, /memory-diag commands/);
assert.match(parsed.usage, /memory-diag revert/);
for (const command of ["health", "quality", "rejections", "disappearances", "trace", "coverage", "audit"]) {
for (const command of ["health", "rejections", "disappearances", "trace", "coverage", "audit"]) {
assert.doesNotMatch(parsed.usage, new RegExp(command));
}
});
@@ -35,7 +36,7 @@ test("unknown command returns usage error", () => {
});
test("removed legacy aliases are ordinary unknown subcommands", () => {
for (const command of ["health", "quality", "rejections", "disappearances", "trace"]) {
for (const command of ["health", "rejections", "disappearances", "trace"]) {
const parsed = parseArgs([command]);
assert.equal(parsed.ok, false, command);
@@ -45,6 +46,41 @@ test("removed legacy aliases are ordinary unknown subcommands", () => {
}
});
test("quality accepts read-only workspace json and display flags", () => {
const parsed = parseArgs(["quality", "--workspace", "/tmp/workspace", "--json", "--verbose", "--raw", "--no-emoji"]);
assert.equal(parsed.ok, true);
assert.equal("command" in parsed && parsed.command, "quality");
assert.equal("options" in parsed && parsed.options.workspace, "/tmp/workspace");
assert.equal("options" in parsed && parsed.options.json, true);
assert.equal("options" in parsed && parsed.options.verbose, true);
assert.equal("options" in parsed && parsed.options.raw, true);
assert.equal("options" in parsed && parsed.options.noEmoji, true);
});
test("quality rejects mutation filter and drill-down flags", () => {
const cases: Array<{ args: string[]; message: string }> = [
{ args: ["quality", "--all"], message: "quality does not accept --all" },
{ args: ["quality", "--apply"], message: "quality does not accept --apply" },
{ args: ["quality", "--memory", "mem-1"], message: "quality does not accept --memory" },
{ args: ["quality", "--event", "evt-1"], message: "quality does not accept --event" },
{ args: ["quality", "--reason", "bad_decision"], message: "quality does not accept rejection filters" },
{ args: ["quality", "--since", "7d"], message: "quality does not accept rejection filters" },
{ args: ["quality", "--soft-only"], message: "quality does not accept rejection filters" },
{ args: ["quality", "--trigger-only"], message: "quality does not accept rejection filters" },
{ args: ["quality", "--include-historical"], message: "quality does not accept --include-historical" },
{ args: ["quality", "--explain"], message: "quality does not accept --explain" },
];
for (const item of cases) {
const parsed = parseArgs(item.args);
assert.equal(parsed.ok, false, item.args.join(" "));
if (parsed.ok) continue;
assert.equal(parsed.message, item.message);
assert.match(parsed.usage, /Usage:/);
}
});
test("hidden maintainer commands are accepted with neutral notices", () => {
const coverage = parseArgs(["coverage"]);
assert.equal(coverage.ok, true);
+628
View File
@@ -0,0 +1,628 @@
import test from "node:test";
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdtempSync } from "node:fs";
import { mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { appendEvidenceEvents, type EvidenceEventInput, type EvidenceEventType, type EvidenceEventV1, type EvidenceOutcome, type EvidencePhase } from "../src/evidence-log.ts";
import { LONG_TERM_LIMITS, type LongTermMemoryEntry, type WorkspaceMemoryStore } from "../src/types.ts";
import { workspaceKey, workspaceMemoryPath } from "../src/paths.ts";
import { buildQualityJSON, formatQualityReviewBoard } from "../scripts/memory-diag/formatters/quality.ts";
import { groupEvidenceByMemoryId } from "../scripts/memory-diag/evidence-model.ts";
import { buildQualityReviewBoard, type ProvenanceClassification, type ReviewBoardReport } from "../scripts/memory-diag/quality-review-model.ts";
import { retentionCandidatesForDiag } from "../scripts/memory-diag/retention-model.ts";
import type { MemoryInspectionReadModel, NormalizedRejection, WorkspaceDiagSnapshot } from "../scripts/memory-diag/types.ts";
const execFileAsync = promisify(execFile);
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const generatedAt = "2026-05-11T12:00:00.000Z";
async function runMemoryDiag(args: string[]): Promise<string> {
const { stdout } = await execFileAsync(process.execPath, [
"--experimental-strip-types",
"scripts/memory-diag.ts",
...args,
], { cwd: repoRoot });
return stdout.trim();
}
test("quality command returns review board skeleton for empty workspace", async () => {
const root = mkdtempSync(join(tmpdir(), "opencode-memory-diag-quality-empty-"));
try {
const stdout = await runMemoryDiag(["quality", "--workspace", root]);
assert.match(stdout, /Memory quality review board/);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("quality formatter returns required human sections in review-board order", () => {
const model = inspectionModel([
entry("mem-section", "Durable formatter section memory", "decision"),
], [
event("evt-section", { type: "render_selected", phase: "render", outcome: "rendered", memory: { memoryId: "mem-section", type: "decision", source: "compaction" } }),
]);
const report = buildQualityReviewBoard(model, {}, generatedAt);
const output = formatQualityReviewBoard(report, {});
assert.match(output, /Memory quality review board/);
assert.match(output, /Purpose: evidence for human\/agent review only; no automatic judgment or cleanup\./);
assert.match(output, /Primary review purpose: SYSTEM MECHANISM observations/);
assert.match(output, /Secondary review purpose: MEMORY CONTENT quality/);
const orderedSections = [
"Evidence provenance",
"Facts - system mechanisms",
"Facts - memory content",
"System mechanism review candidates",
"Memory content review candidates",
"Review questions",
"Next commands",
];
let previous = -1;
for (const section of orderedSections) {
const index = output.indexOf(section);
assert.ok(index > previous, `${section} should appear after the previous section`);
previous = index;
}
});
test("quality formatter shows provenance counts alongside system mechanism facts", () => {
const model = inspectionModel([entry("mem-provenance", "Reabsorbed formatter candidate", "decision")], [], [
rejection("Reabsorbed formatter candidate", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T00:00:00.000Z" }),
]);
const report = buildQualityReviewBoard(model, {}, generatedAt);
const output = formatQualityReviewBoard(report, {});
const factsIndex = output.indexOf("Facts - system mechanisms");
const memoryFactsIndex = output.indexOf("Facts - memory content");
const countsIndex = output.indexOf("Provenance counts for mechanism evidence");
assert.ok(countsIndex > factsIndex);
assert.ok(countsIndex < memoryFactsIndex);
assert.match(output, /reabsorbed_post_rejection=[1-9]\d*/);
});
test("quality human output uses neutral language and keeps raw reason codes in context", () => {
const model = inspectionModel([entry("mem-neutral", "Architecture decision remains durable", "decision")], [], [
rejection("Architecture candidate for parser policy", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T00:00:00.000Z" }),
]);
const report = buildQualityReviewBoard(model, {}, generatedAt);
const output = formatQualityReviewBoard(report, {}).toLowerCase();
for (const forbidden of [
"bad memory",
"delete",
"obsolete",
"should remove",
"must remove",
"false-positive risk",
"clearly_garbage",
"preserving the right memories",
"lacks terminal removal",
"needing review",
"highest-value",
"failing to",
"too rigid",
"noisy candidates",
"when they should remain distinct",
]) {
assert.equal(output.includes(forbidden), false, `human output should not contain ${forbidden}`);
}
for (const line of output.split("\n").filter(line => line.includes("bad_decision"))) {
assert.match(line, /raw reason[- ]codes?/);
}
});
test("quality flattens eviction details and defensively stringifies malformed nested facts", () => {
const model = inspectionModel([], [
event("evt-cap-details", {
type: "memory_removed_capacity",
phase: "storage",
outcome: "removed",
createdAt: "2026-05-11T11:00:00.000Z",
memory: { memoryId: "mem-cap-details", type: "decision", source: "compaction" },
reasonCodes: ["global_cap"],
details: { globalCap: 28, typeCap: 10, malformed: { nested: true } } as unknown as EvidenceEventV1["details"],
}),
]);
const report = buildQualityReviewBoard(model, { verbose: true }, generatedAt);
const candidate = report.reviewCandidates.find(item => item.id === "eviction:evt-cap-details");
assert.ok(candidate);
assert.equal(candidate.facts.globalCap, 28);
assert.equal(candidate.facts.typeCap, 10);
assert.equal(Object.hasOwn(candidate.facts, "details"), false);
const output = formatQualityReviewBoard(report, { verbose: true });
assert.doesNotMatch(output, /\[object Object\]/);
assert.match(output, /globalCap=28/);
assert.match(output, /typeCap=10/);
assert.match(output, /malformed=\{"nested":true\}/);
});
test("quality deduplicates rejection candidates after filtering eligible records", () => {
const model = inspectionModel([], [], [
rejection("Architecture parser policy should remain durable", { type: "decision", reasons: ["temporary_status"], timestamp: "2026-05-09T00:00:00.000Z" }),
rejection("Architecture parser policy should remain durable", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T00:00:00.000Z" }),
]);
const report = buildQualityReviewBoard(model, {}, generatedAt);
const candidates = report.reviewCandidates.filter(candidate => candidate.source === "rejection_rule_evidence");
assert.equal(report.facts.systemMechanisms.rejectionFilters.totalRecords, 2);
assert.equal(report.facts.systemMechanisms.rejectionFilters.uniqueTexts, 1);
assert.equal(candidates.length, 1);
assert.equal(candidates[0].facts.timestamp, "2026-05-10T00:00:00.000Z");
});
test("quality deduplicates eligible rejection candidates newest first", () => {
const model = inspectionModel([], [], [
rejection("Ambiguous architecture memory candidate", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-09T00:00:00.000Z" }),
rejection("Ambiguous architecture memory candidate", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T00:00:00.000Z" }),
]);
const report = buildQualityReviewBoard(model, {}, generatedAt);
const candidates = report.reviewCandidates.filter(candidate => candidate.source === "rejection_rule_evidence");
assert.equal(report.facts.systemMechanisms.rejectionFilters.totalRecords, 2);
assert.equal(report.facts.systemMechanisms.rejectionFilters.uniqueTexts, 1);
assert.equal(candidates.length, 1);
assert.equal(candidates[0].facts.timestamp, "2026-05-10T00:00:00.000Z");
});
test("quality formatter collapses uniform system candidate provenance by displayed group", () => {
const model = inspectionModel([], capacityEvents(3));
const report = buildQualityReviewBoard(model, { verbose: true }, generatedAt);
const output = formatQualityReviewBoard(report, { verbose: true });
const groupLineCount = output.match(/shared provenance for displayed candidates in this group/g)?.length ?? 0;
assert.equal(groupLineCount, 1);
assert.doesNotMatch(output, /^ provenance:/m);
assert.doesNotMatch(output, /not annotated for likely current active-memory content/);
});
test("quality formatter prints standard active-memory questions once in section header", () => {
const model = inspectionModel([
entry("mem-question-1", "Question header active memory one", "decision"),
entry("mem-question-2", "Question header active memory two", "feedback"),
], []);
const report = buildQualityReviewBoard(model, {}, generatedAt);
report.activeMemoryDisplay.items[1].reviewQuestions = [
...report.reviewQuestions.memoryContent,
"Does this memory have additional workspace-specific review context?",
];
const output = formatQualityReviewBoard(report, {});
const activeSection = output.slice(output.indexOf("Memory content review candidates"), output.indexOf("Review questions"));
assert.match(activeSection, /Standard review questions \(applicable to all active memories below\):/);
for (const question of report.reviewQuestions.memoryContent) {
assert.equal(activeSection.match(new RegExp(escapeRegExp(question), "g"))?.length ?? 0, 1);
}
assert.doesNotMatch(activeSection, /^ review questions:/m);
assert.match(activeSection, /^ additional review questions:/m);
assert.match(activeSection, /Does this memory have additional workspace-specific review context\?/);
});
test("quality formatter shows full active memory text under threshold and samples over threshold", () => {
const shortEntries = [
entry("mem-1", "Short active memory one with FULL_TEXT_SENTINEL_1", "feedback"),
entry("mem-2", "Short active memory two with FULL_TEXT_SENTINEL_2", "decision"),
entry("mem-3", "Short active memory three with FULL_TEXT_SENTINEL_3", "project"),
];
const shortOutput = formatQualityReviewBoard(buildQualityReviewBoard(inspectionModel(shortEntries, []), {}, generatedAt), {});
assert.match(shortOutput, /Active memories \(showing all 3 because <= 40\)/);
assert.match(shortOutput, /FULL_TEXT_SENTINEL_3/);
const largeEntries = Array.from({ length: 41 }, (_, index) => entry(
`mem-${index.toString().padStart(2, "0")}`,
`Large active memory ${index} FULL_TEXT_SENTINEL_${index}`,
"feedback",
));
const largeDefault = formatQualityReviewBoard(buildQualityReviewBoard(inspectionModel(largeEntries, []), {}, generatedAt), {});
assert.match(largeDefault, /Showing 40 of 41 active memories\. Use --verbose or --json for all active memory text\./);
const defaultActiveSection = largeDefault.slice(largeDefault.indexOf("Memory content review candidates"), largeDefault.indexOf("Review questions"));
assert.doesNotMatch(defaultActiveSection, /FULL_TEXT_SENTINEL_40/);
const largeVerbose = formatQualityReviewBoard(buildQualityReviewBoard(inspectionModel(largeEntries, []), { verbose: true }, generatedAt), { verbose: true });
assert.match(largeVerbose, /Active memories \(showing all 41 because --verbose\)/);
assert.match(largeVerbose, /FULL_TEXT_SENTINEL_40/);
});
test("quality formatter no-emoji output contains no emoji glyphs", async () => {
const root = mkdtempSync(join(tmpdir(), "opencode-memory-diag-quality-noemoji-"));
try {
await seedWorkspace(root, [entry("mem-noemoji", "No emoji command memory", "feedback")]);
const stdout = await runMemoryDiag(["quality", "--workspace", root, "--no-emoji"]);
assert.doesNotMatch(stdout, /[\u{1F300}-\u{1F9FF}]/u);
assert.match(stdout, /Memory quality review board/);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("quality command json returns report shape and raw mode preserves unredacted text", async () => {
const root = mkdtempSync(join(tmpdir(), "opencode-memory-diag-quality-json-"));
try {
await seedWorkspace(root, [entry("mem-json-cli", "JSON text with token=secret-value and path /Users/alice/project/private.txt", "project")]);
const redacted = JSON.parse(await runMemoryDiag(["quality", "--workspace", root, "--json"]));
assertReviewBoardShape(redacted);
assert.doesNotMatch(JSON.stringify(redacted), /secret-value|\/Users\/alice/);
const raw = JSON.parse(await runMemoryDiag(["quality", "--workspace", root, "--json", "--raw"]));
assertReviewBoardShape(raw);
assert.match(JSON.stringify(raw), /secret-value|\/Users\/alice/);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("buildQualityJSON redacts by default and preserves report shape when raw", () => {
const rawReport = buildQualityReviewBoard(inspectionModel([
entry("mem-json-builder", "Builder text token=secret-value in /Users/alice/private.txt", "project"),
], []), { raw: true }, generatedAt);
const redacted = buildQualityJSON(rawReport, false) as ReviewBoardReport;
assertReviewBoardShape(redacted);
assert.doesNotMatch(JSON.stringify(redacted), /secret-value|\/Users\/alice/);
const raw = buildQualityJSON(rawReport, true) as ReviewBoardReport;
assert.match(JSON.stringify(raw), /secret-value|\/Users\/alice/);
});
test("quality review model applies active memory threshold, full text, and redaction", () => {
const entries = Array.from({ length: 41 }, (_, index) => entry(
`mem-${index.toString().padStart(2, "0")}`,
index === 0
? "Keep full text with token=secret-value and path /Users/alice/project/private.txt for review"
: `Keep full text for active memory ${index}`,
"feedback",
));
const model = inspectionModel(entries, []);
const defaultReport = buildQualityReviewBoard(model, {}, generatedAt);
assert.equal(defaultReport.activeMemoryDisplay.threshold, 40);
assert.equal(defaultReport.activeMemoryDisplay.mode, "sample");
assert.equal(defaultReport.activeMemoryDisplay.shown, 40);
assert.equal(defaultReport.activeMemoryDisplay.total, 41);
assert.equal(defaultReport.activeMemoryDisplay.items.at(-1)?.id, "mem-39");
assert.equal(defaultReport.activeMemoryDisplay.items[0].text.includes("Keep full text with"), true);
assert.doesNotMatch(defaultReport.activeMemoryDisplay.items[0].text, /secret-value|\/Users\/alice/);
assert.match(defaultReport.activeMemoryDisplay.items[0].text, /\[REDACTED\]|<path>/);
const verboseReport = buildQualityReviewBoard(model, { verbose: true }, generatedAt);
assert.equal(verboseReport.activeMemoryDisplay.mode, "all");
assert.equal(verboseReport.activeMemoryDisplay.shown, 41);
const rawReport = buildQualityReviewBoard(model, { raw: true }, generatedAt);
assert.match(rawReport.activeMemoryDisplay.items[0].text, /secret-value|\/Users\/alice/);
});
test("quality provenance counts use all mechanism candidates when human output is representative", () => {
const active = Array.from({ length: 28 }, (_, index) => entry(
`mem-active-${index}`,
`Active display stability memory ${index}`,
"feedback",
));
const events = capacityEvents(12);
const model = inspectionModel(active, events);
const defaultReport = buildQualityReviewBoard(model, {}, generatedAt);
const verboseReport = buildQualityReviewBoard(model, { verbose: true }, generatedAt);
assert.equal(defaultReport.provenanceContext.countsByClassification.unversioned_ambiguous, 12);
assert.equal(verboseReport.provenanceContext.countsByClassification.unversioned_ambiguous, 12);
assert.equal(defaultReport.reviewCandidates.filter(candidate => candidate.mechanism === "eviction_cap").length, 10);
assert.equal(verboseReport.reviewCandidates.filter(candidate => candidate.mechanism === "eviction_cap").length, 12);
assert.equal(defaultReport.provenanceContext.candidateLimit, 10);
assert.deepEqual(defaultReport.provenanceContext.candidateDisplay?.byMechanism.eviction_cap, { shown: 10, total: 12 });
assert.match(formatQualityReviewBoard(defaultReport, {}), /System mechanism review candidates \(representative; 10 shown of 12 total; limit 10 per mechanism category\)/);
assert.equal(defaultReport.activeMemoryDisplay.shown, 28);
assert.equal(defaultReport.activeMemoryDisplay.total, 28);
assert.equal(verboseReport.activeMemoryDisplay.shown, 28);
assert.equal(verboseReport.activeMemoryDisplay.total, 28);
});
test("quality json includes all system mechanism candidates without verbose", async () => {
const root = mkdtempSync(join(tmpdir(), "opencode-memory-diag-quality-json-candidates-"));
try {
await seedWorkspace(root, []);
await appendEvidenceEvents(root, capacityEventInputs(12));
const report = JSON.parse(await runMemoryDiag(["quality", "--workspace", root, "--json"])) as ReviewBoardReport;
const evictionCandidates = report.reviewCandidates.filter(candidate => candidate.mechanism === "eviction_cap");
assert.equal(evictionCandidates.length, 12);
assert.equal(report.provenanceContext.candidateLimit, undefined);
assert.equal(report.activeMemoryDisplay.shown, 0);
assert.equal(report.activeMemoryDisplay.total, 0);
} finally {
await rm(root, { recursive: true, force: true });
}
});
test("quality review model builds system mechanism facts and neutral candidates", () => {
const active = [
entry("mem-a", "Retention architecture uses evidence windows for durable review", "decision"),
entry("mem-b", "Duplicate durable instruction", "feedback"),
entry("mem-c", "Duplicate durable instruction", "feedback"),
entry("old-a", "Superseded same type replacement", "decision", { status: "superseded" }),
entry("old-b", "Superseded cross type replacement", "project", { status: "superseded" }),
];
const events = [
event("evt-reinforced", { type: "memory_reinforced", phase: "reinforcement", outcome: "reinforced", memory: { memoryId: "mem-a", type: "decision", source: "compaction" } }),
event("evt-block-1", { type: "memory_reinforced", phase: "reinforcement", outcome: "rejected", memory: { memoryId: "mem-a", type: "decision", source: "compaction" }, reasonCodes: ["reinforcement_window_blocked"], details: { ref: "1" } }),
event("evt-block-2", { type: "memory_reinforced", phase: "reinforcement", outcome: "rejected", memory: { memoryId: "mem-a", type: "decision", source: "compaction" }, reasonCodes: ["reinforcement_window_blocked"], details: { ref: "2" } }),
event("evt-malformed", { type: "extraction_candidate_rejected", phase: "extraction", outcome: "rejected", reasonCodes: ["invalid_memory_command"] }),
event("evt-cap-type", { type: "memory_removed_capacity", phase: "storage", outcome: "removed", memory: { memoryId: "old-type", type: "decision", source: "compaction" }, reasonCodes: ["type_cap"], createdAt: "2026-05-10T12:00:00.000Z" }),
event("evt-cap-global", { type: "memory_removed_capacity", phase: "storage", outcome: "removed", memory: { memoryId: "old-global", type: "feedback", source: "compaction" }, reasonCodes: ["global_cap"], createdAt: "2026-05-10T11:00:00.000Z", textPreview: "evicted token=secret-value from /tmp/private.txt" }),
event("evt-missing", { type: "promotion_promoted", phase: "promotion", outcome: "promoted", memory: { memoryId: "historical-unknown", type: "reference", source: "compaction" } }),
event("evt-replace-same", { type: "memory_replaced_numbered_ref", phase: "storage", outcome: "superseded", memory: { memoryId: "old-a", type: "decision", source: "compaction" }, reasonCodes: ["same_type_replace"], relations: [{ role: "superseded", memory: { memoryId: "old-a", type: "decision" } }, { role: "superseded_by", memory: { memoryId: "mem-a", type: "decision" } }] }),
event("evt-replace-cross", { type: "memory_replaced_numbered_ref", phase: "storage", outcome: "superseded", memory: { memoryId: "old-b", type: "project", source: "compaction" }, reasonCodes: ["cross_type_replace"], relations: [{ role: "superseded", memory: { memoryId: "old-b", type: "project" } }, { role: "superseded_by", memory: { memoryId: "mem-a", type: "decision" } }] }),
];
const rejections = [
rejection("Retention architecture uses evidence windows for durable review", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T10:00:00.000Z" }),
rejection("Ambiguous useful candidate", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T09:00:00.000Z" }),
rejection("Temporary progress note", { type: "feedback", reasons: ["bad_feedback", "temporary_status"], timestamp: "2026-05-10T08:00:00.000Z" }),
];
const model = inspectionModel(active, events, rejections, { limits: { maxRenderedChars: LONG_TERM_LIMITS.maxRenderedChars, maxEntries: 2 } });
const report = buildQualityReviewBoard(model, {}, generatedAt);
assert.deepEqual(report.facts.systemMechanisms.rejectionFilters.byRawReasonCode, { bad_decision: 2, bad_feedback: 1, temporary_status: 1 });
assert.equal(report.facts.systemMechanisms.rejectionFilters.byType.decision, 2);
assert.equal(report.facts.systemMechanisms.rejectionFilters.ambiguousOrArchitectureLike, 2);
assert.equal(report.facts.systemMechanisms.rejectionFilters.hardReasonOrNoiseHeuristic, 0);
assert.equal(report.facts.systemMechanisms.rejectionFilters.reabsorbedRejectedTexts, 1);
assert.equal(report.facts.systemMechanisms.reinforcementRules.reinforceEvents, 3);
assert.equal(report.facts.systemMechanisms.reinforcementRules.reinforcedEvents, 1);
assert.equal(report.facts.systemMechanisms.reinforcementRules.rejectedOrBlockedEvents, 2);
assert.equal(report.facts.systemMechanisms.reinforcementRules.windowBlockedEvents, 2);
assert.equal(report.facts.systemMechanisms.reinforcementRules.windowBlockRate, 2 / 3);
assert.deepEqual(report.facts.systemMechanisms.reinforcementRules.repeatedBlocksByMemory, [{ memoryId: "mem-a", count: 2, refs: ["1", "2"], rawReasonCodes: ["reinforcement_window_blocked"] }]);
assert.equal(report.facts.systemMechanisms.reinforcementRules.malformedCommandEvents, 1);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.activeMemories, 3);
assert.deepEqual(report.facts.systemMechanisms.evictionAndCaps.fullCaps, ["global"]);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.removedByCapacity, 2);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.removedByGlobalCap, 1);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.removedByTypeCap, 1);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.unknownDisappearances, 1);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.recentEvictionsByType.decision, 1);
assert.equal(report.facts.systemMechanisms.evictionAndCaps.recentEvictedContentShown, 2);
assert.equal(report.facts.systemMechanisms.identityAndDedup.replacementEvents, 2);
assert.equal(report.facts.systemMechanisms.identityAndDedup.sameTypeReplacementEvents, 1);
assert.equal(report.facts.systemMechanisms.identityAndDedup.crossTypeReplacementEvents, 1);
assert.equal(report.facts.systemMechanisms.identityAndDedup.duplicateTextOrIdentityGroups, 1);
assert.ok(report.reviewCandidates.some(candidate => candidate.source === "reabsorption_evidence" && candidate.provenance?.classification === "reabsorbed_post_rejection"));
assert.ok(report.reviewCandidates.some(candidate => candidate.mechanism === "reinforcement_rule"));
assert.ok(report.reviewCandidates.some(candidate => candidate.source === "eviction_cap_evidence" && candidate.evidence.textAvailable === false));
assert.ok(report.reviewCandidates.some(candidate => candidate.source === "identity_dedup_evidence"));
assert.ok(report.reviewCandidates.every(candidate => Array.isArray(candidate.heuristicFlags) && Array.isArray(candidate.reviewQuestions)));
assert.ok(report.reviewCandidates.filter(candidate => candidate.source !== "active_memory").every(candidate => candidate.provenance));
assert.doesNotMatch(JSON.stringify(report), /secret-value|\/tmp\/private/);
});
test("quality review model includes provenance timeline and classification counts", () => {
const active = [entry("mem-active", "Reabsorbed post rejection candidate", "decision")];
const events = [
event("evt-migration-1", { type: "memory_migration_superseded", phase: "storage", outcome: "superseded", createdAt: "2026-05-01T00:00:00.000Z", details: { migrationId: "2026-05-01-retention-clock-backfill" } }),
event("evt-before", { type: "memory_removed_capacity", phase: "storage", outcome: "removed", createdAt: "2026-04-30T00:00:00.000Z", memory: { memoryId: "before", type: "decision", source: "compaction" }, reasonCodes: ["global_cap"] }),
event("evt-before-unknown", { type: "promotion_promoted", phase: "promotion", outcome: "promoted", createdAt: "2026-04-30T01:00:00.000Z", memory: { memoryId: "before-unknown", type: "reference", source: "compaction" } }),
event("evt-after", { type: "memory_removed_capacity", phase: "storage", outcome: "removed", createdAt: "2026-05-11T11:00:00.000Z", memory: { memoryId: "after", type: "feedback", source: "compaction" }, reasonCodes: ["type_cap"] }),
];
const rejections = [
rejection("Legacy unscoped architecture rule", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-04-29T00:00:00.000Z", legacy: true }),
rejection("Reabsorbed post rejection candidate", { type: "decision", reasons: ["bad_decision"], timestamp: "2026-05-10T00:00:00.000Z" }),
];
const model = inspectionModel(active, events, rejections, {
migrations: ["2026-04-26-p0-cleanup", "2026-04-28-quality-cleanup", "2026-05-01-retention-clock-backfill"],
lastActivityAt: "2026-05-10T00:00:00.000Z",
});
const report = buildQualityReviewBoard(model, {}, generatedAt);
const byMigration = new Map(report.provenanceContext.migrationTimeline.map(row => [row.migrationId, row]));
assert.equal(report.provenanceContext.method, "migration_timestamp_and_format_inference");
assert.equal(report.provenanceContext.producerVersionAvailable, false);
assert.equal(report.provenanceContext.falseCurrentRiskBias, "prefer_unversioned_ambiguous_when_uncertain");
assert.equal(byMigration.get("2026-04-26-p0-cleanup")?.presentInStore, true);
assert.equal(byMigration.get("2026-05-01-retention-clock-backfill")?.firstEvidenceAt, "2026-05-01T00:00:00.000Z");
assert.equal(report.provenanceContext.lastActivityAt, "2026-05-10T00:00:00.000Z");
for (const classification of provenanceClassifications()) {
assert.equal(typeof report.provenanceContext.countsByClassification[classification], "number");
}
assert.ok(report.provenanceContext.countsByClassification.legacy_unversioned_format >= 1);
assert.ok(report.provenanceContext.countsByClassification.reabsorbed_post_rejection >= 1);
assert.ok(report.provenanceContext.countsByClassification.suspected_pre_migration_legacy >= 1);
assert.ok(report.provenanceContext.countsByClassification.likely_current_behavior >= 1);
});
test("quality review model exposes required JSON shape with neutral language", () => {
const model = inspectionModel([entry("mem-json", "Durable JSON shape memory", "project")], [
event("evt-json", { type: "render_selected", phase: "render", outcome: "rendered", memory: { memoryId: "mem-json", type: "project", source: "compaction" } }),
]);
const report = buildQualityReviewBoard(model, {}, generatedAt);
assertReviewBoardShape(report);
const serialized = JSON.stringify(report).toLowerCase();
for (const forbidden of ["bad memory", "delete", "obsolete", "should remove"]) {
assert.equal(serialized.includes(forbidden), false, `report should not contain ${forbidden}`);
}
});
function entry(id: string, text: string, type: LongTermMemoryEntry["type"], overrides: Partial<LongTermMemoryEntry> = {}): LongTermMemoryEntry {
return {
id,
type,
text,
source: "compaction",
confidence: 0.75,
status: "active",
createdAt: "2026-05-01T00:00:00.000Z",
updatedAt: "2026-05-10T00:00:00.000Z",
retentionClock: new Date("2026-05-10T00:00:00.000Z").getTime(),
...overrides,
};
}
function event(
eventId: string,
overrides: Partial<EvidenceEventV1> & { type: EvidenceEventType; phase: EvidencePhase; outcome: EvidenceOutcome },
): EvidenceEventV1 {
return {
version: 1,
eventId,
createdAt: "2026-05-11T00:00:00.000Z",
workspaceKey: "workspace-key",
workspaceRootHash: "workspace-root-hash",
reasonCodes: [],
...overrides,
};
}
function capacityEvents(count: number): EvidenceEventV1[] {
return Array.from({ length: count }, (_, index) => event(`evt-cap-${index.toString().padStart(2, "0")}`, {
type: "memory_removed_capacity",
phase: "storage",
outcome: "removed",
createdAt: `2026-05-11T${String(index).padStart(2, "0")}:00:00.000Z`,
memory: { memoryId: `evicted-${index}`, type: "feedback", source: "compaction" },
reasonCodes: ["global_cap"],
}));
}
function capacityEventInputs(count: number): EvidenceEventInput[] {
return Array.from({ length: count }, (_, index) => ({
type: "memory_removed_capacity",
phase: "storage",
outcome: "removed",
memory: { memoryId: `evicted-${index}`, type: "feedback", source: "compaction" },
reasonCodes: ["global_cap"],
}));
}
function rejection(text: string, options: { type: NormalizedRejection["type"]; reasons: string[]; timestamp: string; legacy?: boolean }): NormalizedRejection {
return {
timestamp: options.timestamp,
workspaceKey: options.legacy ? undefined : "workspace-key",
workspaceRoot: undefined,
workspaceRootHash: options.legacy ? undefined : "workspace-root-hash",
type: options.type,
source: "compaction",
origin: "compaction_candidate",
fromTrigger: false,
text,
reasons: options.reasons,
};
}
function inspectionModel(
entries: LongTermMemoryEntry[],
events: EvidenceEventV1[],
rejectionRecords: NormalizedRejection[] = [],
storeOverrides: Partial<WorkspaceMemoryStore> = {},
): MemoryInspectionReadModel {
const store: WorkspaceMemoryStore = {
version: 1,
workspace: { root: "/tmp/workspace", key: "workspace-key" },
limits: { maxRenderedChars: LONG_TERM_LIMITS.maxRenderedChars, maxEntries: LONG_TERM_LIMITS.maxEntries },
entries,
migrations: [],
updatedAt: generatedAt,
lastActivityAt: "2026-05-10T00:00:00.000Z",
...storeOverrides,
};
const retention = retentionCandidatesForDiag(store, new Date(generatedAt).getTime());
const snapshot: WorkspaceDiagSnapshot = {
store,
journal: { version: 1, workspace: { root: "", key: "" }, entries: [], updatedAt: new Date(0).toISOString() },
retention,
memories: [],
recentEvents: [],
allEvents: events,
summary: { storedActive: entries.length, rendered: retention.rendered.length, pending: 0, rejectedLast7Days: 0, corruptStoresQuarantinedLast30Days: 0 },
};
return {
snapshot,
store,
pending: snapshot.journal,
evidenceEvents: events,
rejectionRecords,
currentById: new Map(entries.map(memory => [memory.id, memory])),
evidenceByMemoryId: groupEvidenceByMemoryId(events),
};
}
function provenanceClassifications(): ProvenanceClassification[] {
return [
"explicit_migration_evidence",
"legacy_unversioned_format",
"reabsorbed_post_rejection",
"suspected_pre_migration_legacy",
"likely_current_behavior",
"unversioned_ambiguous",
];
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
async function seedWorkspace(root: string, entries: LongTermMemoryEntry[]): Promise<void> {
const key = await workspaceKey(root);
const memoryPath = await workspaceMemoryPath(root);
const store: WorkspaceMemoryStore = {
version: 1,
workspace: { root, key },
limits: { maxRenderedChars: LONG_TERM_LIMITS.maxRenderedChars, maxEntries: LONG_TERM_LIMITS.maxEntries },
entries,
migrations: [],
updatedAt: generatedAt,
lastActivityAt: "2026-05-10T00:00:00.000Z",
};
await mkdir(dirname(memoryPath), { recursive: true });
await writeFile(memoryPath, JSON.stringify(store, null, 2));
}
function assertReviewBoardShape(report: ReviewBoardReport): void {
assert.equal(report.version, 1);
assert.equal(typeof report.generatedAt, "string");
assert.equal(typeof report.workspace.rootHash, "string");
assert.equal(typeof report.workspace.key, "string");
assert.equal(report.purpose, "review_evidence_only");
assert.equal(report.languageGuidance.nonAuthoritative, true);
assert.equal(report.languageGuidance.mutation, "none");
assert.equal(report.languageGuidance.rawReasonCodesAreEvidence, true);
assert.equal(report.languageGuidance.producerVersionRecorded, false);
assert.equal(report.languageGuidance.provenanceInferenceOnly, true);
assert.equal(report.languageGuidance.primaryReviewPurpose, "system_mechanism_observations");
assert.equal(report.languageGuidance.secondaryReviewPurpose, "memory_content_quality");
assert.ok(Array.isArray(report.provenanceContext.migrationTimeline));
assert.equal(typeof report.provenanceContext.countsByClassification.unversioned_ambiguous, "number");
assert.equal(typeof report.facts.systemMechanisms.rejectionFilters.totalRecords, "number");
assert.equal(typeof report.facts.systemMechanisms.reinforcementRules.windowBlockRate, "number");
assert.ok(Array.isArray(report.facts.systemMechanisms.evictionAndCaps.fullCaps));
assert.equal(typeof report.facts.systemMechanisms.identityAndDedup.duplicateTextOrIdentityGroups, "number");
assert.equal(typeof report.facts.memoryContent.evidenceCoverage.covered, "number");
assert.ok(Array.isArray(report.activeMemoryDisplay.items));
assert.ok(Array.isArray(report.reviewCandidates));
assert.ok(Array.isArray(report.reviewQuestions.systemMechanism));
assert.ok(Array.isArray(report.reviewQuestions.memoryContent));
assert.ok(Array.isArray(report.nextCommands));
}
+1 -1
View File
@@ -214,7 +214,7 @@ test("memory-diag defaults to status when no subcommand is supplied", async () =
test("removed legacy aliases return unknown subcommand", async () => {
const root = mkdtempSync(join(tmpdir(), "opencode-memory-diag-legacy-health-"));
try {
for (const command of ["health", "quality", "rejections", "disappearances", "trace"]) {
for (const command of ["health", "rejections", "disappearances", "trace"]) {
await assert.rejects(
runMemoryDiagResult([command, "--workspace", root]),
(error: unknown) => {