Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
8c4fb25
feat: add durable memory lifecycle and archive mutations
Boulea7 Mar 26, 2026
3ddeacf
fix: make rollout selection deterministic
Boulea7 Mar 26, 2026
54d663f
fix: resolve foundation review gaps
Boulea7 Apr 7, 2026
4bc384b
fix: harden planned snapshot coverage and rollout ordering
Boulea7 Apr 8, 2026
8a7a328
feat: add Codex integration stack surfaces
Boulea7 Mar 26, 2026
622844e
docs: sync Codex-first hybrid integration contracts
Boulea7 Mar 26, 2026
e1ac20e
feat: close codex integration workflow contract gaps
Boulea7 Mar 26, 2026
e535117
feat: tighten codex workflow contract and release gates
Boulea7 Mar 26, 2026
0883967
feat: tighten workflow contract and post-work review
Boulea7 Mar 26, 2026
c0890e9
feat: clarify skill readiness and blocked apply contracts
Boulea7 Mar 26, 2026
71e5532
fix: tighten integration review contracts
Boulea7 Apr 7, 2026
427d05e
feat: pin hook helpers and add retrieval sidecar index
Boulea7 Mar 26, 2026
632c99f
feat: harden retrieval sidecar diagnostics and provenance
Boulea7 Mar 26, 2026
18a3d01
feat: surface retrieval health and audit diagnostics
Boulea7 Mar 27, 2026
bb67222
feat: add retrieval reindex and lineage reviewer warnings
Boulea7 Mar 27, 2026
de8b835
fix: harden issue5 memory safety and release gates
Boulea7 Mar 27, 2026
7d8cfcd
feat: clarify retrieval fallback contract and integration json
Boulea7 Mar 27, 2026
3c9ae78
fix: align retrieval sidecar stack contracts
Boulea7 Apr 7, 2026
292dfe8
fix: harden retrieval hook quoting and audit fallback
Boulea7 Apr 8, 2026
aae1e76
feat: add manual mutation reviewer contracts
Boulea7 Mar 27, 2026
a517417
feat: deepen manual mutation reviewer lifecycle contracts
Boulea7 Mar 28, 2026
c7d1bad
test: align reviewer workflow print-config contracts
Boulea7 Apr 8, 2026
937a90b
fix: resolve PR4 reviewer and workflow contract gaps
Boulea7 Apr 8, 2026
cd639fe
fix: keep rollout provenance additive
Boulea7 Apr 3, 2026
3706562
feat: tighten continuity startup contract
Boulea7 Apr 4, 2026
5f457cc
fix: narrow continuity signal conflicts and latest goals
Boulea7 Apr 5, 2026
91f5217
feat: gate durable sync to primary rollouts
Boulea7 Apr 3, 2026
db65236
feat: tighten continuity evidence and low-signal guards
Boulea7 Apr 4, 2026
18e4fc3
fix: reconcile continuity rollout contracts
Boulea7 Apr 8, 2026
27dc37e
fix: tighten continuity warning provenance
Boulea7 Apr 8, 2026
25b6bfd
feat: rebuild startup reviewer provenance wave
Boulea7 Apr 8, 2026
477ad23
fix: restore rejected audit reviewer contract
Boulea7 Apr 8, 2026
1ca49a0
fix: restore PR6 recovery validation gates
Boulea7 Apr 8, 2026
9eaba85
feat: tighten extraction trust signals
Boulea7 Apr 4, 2026
4568696
fix: tighten durable corrections and command signatures
Boulea7 Apr 5, 2026
30f1839
feat: harden directive extraction and unsafe topic recall
Boulea7 Apr 5, 2026
c76a19c
feat: harden directive extraction and continuity conflict hints
Boulea7 Apr 6, 2026
a418c97
fix: preserve directive clause parsing and reference topics
Boulea7 Apr 8, 2026
4b7027f
fix: refine extractor conflict and continuity semantics
Boulea7 Apr 8, 2026
55e65aa
docs: sync codex release-facing command examples
Boulea7 Apr 8, 2026
9447cd9
fix: align route truth and codex integration contracts
Boulea7 Apr 9, 2026
9c1b5de
test: stabilize doctor app-server contract
Boulea7 Apr 9, 2026
aed76c1
test: align route contract expectations
Boulea7 Apr 9, 2026
a227e68
test: refresh compiled contract coverage
Boulea7 Apr 9, 2026
4c72e4d
test: align packaged contract coverage
Boulea7 Apr 9, 2026
f4150c2
fix: harden release-facing contract closure
Boulea7 Apr 9, 2026
60f28f9
fix: tighten continuity selectors and memory contract closure
Boulea7 Apr 9, 2026
e293df6
fix: retry continuity recovery before newer rollouts
Boulea7 Apr 9, 2026
00addbd
docs: clarify integrations install AGENTS boundary
Boulea7 Apr 9, 2026
8b93a43
docs: add repository AGENTS guide
Boulea7 Apr 9, 2026
72fcd85
docs: clarify Claude and Gemini host boundaries
Boulea7 Apr 10, 2026
459e203
fix: harden runtime contract remediation
Boulea7 Apr 10, 2026
32e6860
test: refresh recovery record scope expectation
Boulea7 Apr 11, 2026
e179cae
test: align compiled staged-write failure contract
Boulea7 Apr 11, 2026
fad8b57
fix: close runtime safety seams
Boulea7 Apr 10, 2026
94ba1c9
fix: make init explicit and idempotent
Boulea7 Apr 10, 2026
cc2ea2f
fix: keep session inspection read-only
Boulea7 Apr 10, 2026
5ecc00b
fix: keep init independent from global config state
Boulea7 Apr 11, 2026
820c355
test: isolate init env overrides
Boulea7 Apr 11, 2026
119d0c3
test: keep vitest config coverage lint-safe
Boulea7 Apr 10, 2026
4e33abd
fix: keep worktree exclusions in vitest config
Boulea7 Apr 11, 2026
f337d90
fix: preserve vitest default excludes
Boulea7 Apr 11, 2026
678694f
fix: preserve same-host issue tracker refs
Boulea7 Apr 11, 2026
fbef294
fix: fail closed for invalid integration doctor cwd
Boulea7 Apr 11, 2026
62b7301
fix: keep issue tracker keys repo-aware
Boulea7 Apr 11, 2026
805f319
fix: close issue5 tail closeout blockers
Boulea7 Apr 11, 2026
a7b34ef
Merge pull request #26 from Boulea7/fix/issue5-tail-closeout-unblock-…
Boulea7 Apr 12, 2026
7630066
Merge pull request #25 from Boulea7/pr/issue5-19-shared-cwd-fail-closed
Boulea7 Apr 12, 2026
640de88
Merge pull request #24 from Boulea7/pr/issue5-18-closeout-review-debt
Boulea7 Apr 12, 2026
03d0b4e
Merge pull request #23 from Boulea7/pr/issue5-17-release-gate-isolati…
Boulea7 Apr 12, 2026
076eccb
Merge pull request #22 from Boulea7/pr/issue5-16-init-idempotency-force
Boulea7 Apr 12, 2026
50f6bc2
Merge pull request #21 from Boulea7/pr/issue5-15-session-readonly-clo…
Boulea7 Apr 12, 2026
6f11e67
Merge pull request #20 from Boulea7/pr/issue5-14-runtime-safety-closure
Boulea7 Apr 12, 2026
961945f
Merge pull request #19 from Boulea7/pr/issue5-13-runtime-contract-rem…
Boulea7 Apr 12, 2026
a1c8210
Merge pull request #18 from Boulea7/pr/issue5-12-claude-gemini-host-b…
Boulea7 Apr 12, 2026
e530909
Merge pull request #17 from Boulea7/pr/issue5-11-help-and-boundary-cl…
Boulea7 Apr 12, 2026
13f9d7f
Merge pull request #16 from Boulea7/pr/issue5-10-wave3-contract-closure
Boulea7 Apr 12, 2026
6764bc1
Merge pull request #15 from Boulea7/pr/issue5-09-release-contract-clo…
Boulea7 Apr 12, 2026
04a66ba
Merge pull request #14 from Boulea7/pr/issue5-08-route-truth-contracts
Boulea7 Apr 12, 2026
9d3f1dc
Merge branch 'pr/issue5-07-extractor-default-closure' into pr/issue5-…
Boulea7 Apr 12, 2026
ff99366
Merge branch 'pr/issue5-06-startup-reviewer-provenance-v3' into pr/is…
Boulea7 Apr 12, 2026
82fde42
Merge pull request #11 from Boulea7/pr/issue5-05-continuity-rollout-s…
Boulea7 Apr 12, 2026
31c3fbd
Merge pull request #10 from Boulea7/pr/issue5-04-manual-mutation-revi…
Boulea7 Apr 12, 2026
b70d256
Merge pull request #9 from Boulea7/pr/issue5-03-retrieval-sidecar
Boulea7 Apr 12, 2026
670a04e
Merge pull request #8 from Boulea7/pr/issue5-02-integration-foundation
Boulea7 Apr 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/lib/cli/register-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ export function registerCommands(program: Command): void {

program
.command("forget")
.description("Delete matching memory entries")
.description("Delete or archive matching memory entries")
.argument("<query>", "Search query used to find memory entries")
.option("--scope <scope>", "Specific scope to target, or all")
.option("--archive", "Move matching entries into archive instead of deleting them")
.action(withStdout(async (query, options) => runForget(query, options)));

program
Expand Down
7 changes: 5 additions & 2 deletions src/lib/commands/forget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import type { MemoryScope } from "../types.js";
interface ForgetOptions {
cwd?: string;
scope?: MemoryScope | "all";
archive?: boolean;
}

export async function runForget(
query: string,
options: ForgetOptions = {}
): Promise<string> {
const runtime = await buildRuntimeContext(options.cwd);
const deleted = await runtime.syncService.memoryStore.forget(options.scope ?? "all", query);
const deleted = await runtime.syncService.memoryStore.forget(options.scope ?? "all", query, {
archive: options.archive
});
if (deleted.length === 0) {
return `No memory entries matched "${query}".`;
}

return [
`Deleted ${deleted.length} memory entr${deleted.length === 1 ? "y" : "ies"}:`,
`${options.archive ? "Archived" : "Deleted"} ${deleted.length} memory entr${deleted.length === 1 ? "y" : "ies"}:`,
...deleted.map((entry) => `- ${entry.scope}/${entry.topic}/${entry.id}: ${entry.summary}`)
].join("\n");
}
11 changes: 10 additions & 1 deletion src/lib/commands/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function formatPendingSyncRecovery(record: SyncRecoveryRecord, recoveryPath: str
`- Rollout: ${record.rolloutPath}`,
`- Session: ${record.sessionId ?? "unknown"}`,
`- Status: ${record.status} (${record.appliedCount} operation${record.appliedCount === 1 ? "" : "s"})`,
`- No-op: ${record.noopOperationCount ?? 0}`,
`- Suppressed: ${record.suppressedOperationCount ?? 0}`,
`- Audit entry written: ${record.auditEntryWritten}`,
`- Failure: ${record.failureMessage}`
Expand Down Expand Up @@ -62,6 +63,7 @@ function syncAuditSignature(entry: MemorySyncAuditEntry): string {
actualExtractorMode: entry.actualExtractorMode,
actualExtractorName: entry.actualExtractorName,
appliedCount: entry.appliedCount,
noopOperationCount: entry.noopOperationCount ?? 0,
suppressedOperationCount: entry.suppressedOperationCount ?? 0,
scopesTouched: entry.scopesTouched,
conflicts: entry.conflicts ?? [],
Expand Down Expand Up @@ -131,7 +133,14 @@ export async function runMemory(options: MemoryOptions = {}): Promise<string> {
? await runtime.syncService.memoryStore.readRecentSyncAuditEntries(recentCount * 2)
: [];
const recentSyncAudit = recentSyncAuditPreviewEntries.slice(0, recentCount);
const pendingSyncRecovery = await runtime.syncService.memoryStore.readSyncRecoveryRecord();
let pendingSyncRecovery = null;
try {
pendingSyncRecovery = await runtime.syncService.memoryStore.readSyncRecoveryRecord();
} catch (error) {
runtime.loadedConfig.warnings.push(
`Sync recovery record could not be read: ${error instanceof Error ? error.message : String(error)}`
);
}
const startupFilesByScope = {
global: startup.sourceFiles.filter(
(filePath) => filePath === runtime.syncService.memoryStore.getMemoryFile("global")
Expand Down
6 changes: 5 additions & 1 deletion src/lib/commands/remember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function runRemember(
const details = options.detail?.length ? options.detail : [text];
const id = slugify(text);

await runtime.syncService.memoryStore.remember(
const record = await runtime.syncService.memoryStore.remember(
scope,
topic,
id,
Expand All @@ -28,5 +28,9 @@ export async function runRemember(
"Manual remember request."
);

if (record?.lifecycleAction === "noop") {
return `Memory ${scope}/${topic}/${id} is already up to date.`;
}

return `Saved memory to ${scope}/${topic} with id ${id}.`;
}
16 changes: 2 additions & 14 deletions src/lib/commands/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { persistSessionContinuity } from "../domain/session-continuity-persisten
import {
listRolloutFiles,
parseRolloutEvidence,
readRolloutMeta
selectLatestPrimaryRolloutFromCandidates
} from "../domain/rollout.js";
import { compileSessionContinuity } from "../domain/session-continuity.js";
import { readCodexBaseInstructions } from "../runtime/codex-config.js";
Expand All @@ -16,18 +16,6 @@ import { WrapperRuntimeInjector } from "../runtime/wrapper-injector.js";
const sessionSource = new RolloutSessionSource();
const runtimeInjector = new WrapperRuntimeInjector();

async function selectLatestPrimaryRollout(candidates: string[]): Promise<string | null> {
const metas = await Promise.all(candidates.map((candidate) => readRolloutMeta(candidate)));
for (let index = candidates.length - 1; index >= 0; index -= 1) {
if (metas[index]?.isSubagent === true) {
continue;
}
return candidates[index] ?? null;
}

return null;
}

async function syncRecentRollouts(
cwd: string,
before: string[],
Expand Down Expand Up @@ -100,7 +88,7 @@ async function saveSessionContinuity(
startedAtMs,
endedAtMs
);
const rolloutPath = await selectLatestPrimaryRollout(candidates);
const rolloutPath = await selectLatestPrimaryRolloutFromCandidates(candidates);
if (!rolloutPath) {
return null;
}
Expand Down
109 changes: 109 additions & 0 deletions src/lib/domain/memory-lifecycle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import type {
MemoryEntry,
MemoryHistoryRecordState,
MemoryLifecycleAction,
MemoryRecordState,
MemoryRef,
MemoryScope
} from "../types.js";

const allowedScopes = new Set<MemoryScope>(["global", "project", "project-local"]);
const allowedStates = new Set<MemoryRecordState>(["active", "archived"]);
const allowedHistoryStates = new Set<MemoryHistoryRecordState>(["active", "archived", "deleted"]);

export function buildMemoryRef(
scope: MemoryScope,
state: MemoryRecordState,
topic: string,
id: string
): string {
return `${scope}:${state}:${topic}:${id}`;
}

export function parseMemoryRef(ref: string): MemoryRef | null {
const [scope, state, topic, ...idParts] = ref.split(":");
const id = idParts.join(":");
if (
!scope ||
!state ||
!topic ||
idParts.length === 0 ||
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
id.length === 0 ||
!allowedScopes.has(scope as MemoryScope) ||
!allowedStates.has(state as MemoryRecordState)
) {
return null;
}

return {
ref,
scope: scope as MemoryScope,
state: state as MemoryRecordState,
topic,
id
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

export function assertValidMemoryRef(ref: string): MemoryRef {
const parsed = parseMemoryRef(ref);
if (!parsed) {
throw new Error(
`Invalid memory ref "${ref}". Expected format <scope>:<state>:<topic>:<id>.`
);
}

return parsed;
}

export function isMemoryHistoryRecordState(value: unknown): value is MemoryHistoryRecordState {
return typeof value === "string" && allowedHistoryStates.has(value as MemoryHistoryRecordState);
}

function normalizeString(value: string | undefined): string {
return (value ?? "").trim();
}

function normalizeStringArray(values: readonly string[]): string[] {
return values
.map((value) => value.trim())
.filter(Boolean);
}

export function areEquivalentMemoryEntries(left: MemoryEntry, right: MemoryEntry): boolean {
return (
normalizeString(left.summary) === normalizeString(right.summary) &&
JSON.stringify(normalizeStringArray(left.details)) ===
JSON.stringify(normalizeStringArray(right.details)) &&
JSON.stringify(normalizeStringArray(left.sources)) ===
JSON.stringify(normalizeStringArray(right.sources)) &&
normalizeString(left.reason) === normalizeString(right.reason)
);
}

export function classifyUpsertLifecycle(
existingActive: MemoryEntry | null,
existingArchived: MemoryEntry | null,
nextEntry: MemoryEntry
): Extract<MemoryLifecycleAction, "add" | "update" | "noop"> {
if (existingActive && areEquivalentMemoryEntries(existingActive, nextEntry)) {
return "noop";
}

return existingActive || existingArchived ? "update" : "add";
}

export function nextHistoryStateForLifecycle(
action: MemoryLifecycleAction
): MemoryHistoryRecordState | undefined {
switch (action) {
case "add":
case "update":
return "active";
case "archive":
return "archived";
case "delete":
return "deleted";
case "noop":
return undefined;
}
}
Loading
Loading