feat: harden continuity source hygiene and refresh workflows#3
Conversation
Reviewer's GuideIntroduces a new Updated class diagram for session continuity store and related typesclassDiagram
direction LR
class SessionContinuityStore {
-project: ProjectContext
-config: AppConfig
-paths: SessionContinuityPaths
+saveSummary(summary: SessionContinuitySummary, scope: SessionContinuityScope_or_both) string[]
+replaceSummary(summary: SessionContinuitySummary, scope: SessionContinuityScope_or_both) string[]
+clear(scope: SessionContinuityScope_or_both) string[]
+readState(scope: SessionContinuityScope) SessionContinuityState_or_null
+readRecentAuditEntries(limit: number) SessionContinuityAuditEntry[]
+readLatestAuditEntry() SessionContinuityAuditEntry_or_null
+readLatestAuditEntryMatchingScope(scope: SessionContinuityScope_or_both) SessionContinuityAuditEntry_or_null
+writeRecoveryRecord(record: ContinuityRecoveryRecord) void
+readRecoveryRecord() ContinuityRecoveryRecord_or_null
+getLocalIgnorePath() string
+ensureSharedLayout() void
+ensureLocalLayout() void
+ensureLocalIgnore() void
%% new private helpers
-writeSummary(summary: SessionContinuitySummary, scope: SessionContinuityScope_or_both, writeMode: SessionContinuityWriteMode) string[]
-readAuditEntries() SessionContinuityAuditEntry[]
-resolveLocalWritePath(writeMode: SessionContinuityWriteMode) string
}
class SessionContinuitySummary {
+project: SessionContinuityLayerSummary
+projectLocal: SessionContinuityLayerSummary
+sourceSessionId: string
}
class SessionContinuityState {
+status: string
+sourceSessionId: string
+updatedAt: string
+goal: string
+confirmedWorking: string[]
+triedAndFailed: string[]
+notYetTried: string[]
+incompleteNext: string[]
+filesDecisionsEnvironment: string[]
}
class SessionContinuityLayerSummary {
+goal: string
+confirmedWorking: string[]
+triedAndFailed: string[]
+notYetTried: string[]
+incompleteNext: string[]
+filesDecisionsEnvironment: string[]
}
class SessionContinuityWriteMode {
<<enumeration>>
merge
replace
}
class SessionContinuityAuditTrigger {
<<enumeration>>
manual_save
manual_refresh
wrapper_auto_save
}
class SessionContinuityScope {
<<enumeration>>
project
project_local
}
class SessionContinuityAuditEntry {
+generatedAt: string
+projectId: string
+worktreeId: string
+configuredExtractorMode: SessionContinuityExtractorPath
+trigger: SessionContinuityAuditTrigger_optional
+writeMode: SessionContinuityWriteMode_optional
+scope: SessionContinuityScope_or_both
+rolloutPath: string
+sourceSessionId: string
+preferredPath: SessionContinuityExtractorPath
+actualPath: SessionContinuityExtractorPath
+fallbackReason: SessionContinuityFallbackReason_optional
+evidenceCounts: SessionContinuityEvidenceCounts
+writtenPaths: string[]
}
class ContinuityRecoveryRecord {
+generatedAt: string
+projectId: string
+worktreeId: string
+rolloutPath: string
+sourceSessionId: string
+trigger: SessionContinuityAuditTrigger_optional
+writeMode: SessionContinuityWriteMode_optional
+scope: SessionContinuityScope_or_both
+writtenPaths: string[]
+preferredPath: SessionContinuityExtractorPath
+actualPath: SessionContinuityExtractorPath
+fallbackReason: SessionContinuityFallbackReason_optional
+evidenceCounts: SessionContinuityEvidenceCounts
+failedStage: ContinuityRecoveryFailedStage
+failureMessage: string
}
class RolloutMeta {
+sessionId: string
+createdAt: string
+createdAtMs: number
+cwd: string
+rolloutPath: string
+isSubagent: boolean_optional
+forkedFromSessionId: string_optional
}
class RolloutEvidence {
+sessionId: string
+createdAt: string
+cwd: string
+userMessages: string[]
+agentMessages: string[]
+toolCalls: RolloutToolCall[]
+rolloutPath: string
+isSubagent: boolean_optional
+forkedFromSessionId: string_optional
}
class SessionContinuityDiagnostics {
+generatedAt: string
+rolloutPath: string
+sourceSessionId: string
+preferredPath: SessionContinuityExtractorPath
+actualPath: SessionContinuityExtractorPath
+fallbackReason: SessionContinuityFallbackReason_optional
+evidenceCounts: SessionContinuityEvidenceCounts
+writtenPaths: string[]
}
class SessionContinuityExtractorPath {
<<enumeration>>
codex
heuristic
}
class SessionContinuityFallbackReason {
<<enumeration>>
none
codex_failed
codex_empty
codex_malformed
heuristic_empty
}
class SessionContinuityEvidenceCounts {
+commands: number
+fileWrites: number
+nextSteps: number
+untriedIdeas: number
}
class buildSessionContinuityAuditEntry {
+buildSessionContinuityAuditEntry(project: ProjectContext, config: AppConfig, diagnostics: SessionContinuityDiagnostics, writtenPaths: string[], scope: SessionContinuityScope_or_both, options: BuildSessionContinuityAuditEntryOptions) SessionContinuityAuditEntry
+normalizeSessionContinuityAuditTrigger(trigger: SessionContinuityAuditTrigger_optional) SessionContinuityAuditTrigger_or_legacy
+normalizeSessionContinuityWriteMode(writeMode: SessionContinuityWriteMode_optional) SessionContinuityWriteMode
}
class BuildSessionContinuityAuditEntryOptions {
+trigger: SessionContinuityAuditTrigger_optional
+writeMode: SessionContinuityWriteMode_optional
}
class buildContinuityRecoveryRecord {
+buildContinuityRecoveryRecord(options: BuildContinuityRecoveryRecordOptions) ContinuityRecoveryRecord
}
class BuildContinuityRecoveryRecordOptions {
+projectId: string
+worktreeId: string
+diagnostics: SessionContinuityDiagnostics
+trigger: SessionContinuityAuditTrigger_optional
+writeMode: SessionContinuityWriteMode_optional
+scope: SessionContinuityScope_or_both
+writtenPaths: string[]
+failedStage: ContinuityRecoveryFailedStage
+failureMessage: string
}
class session_continuity_layer_ops {
+applySessionContinuityLayerSummary(base: SessionContinuityState, summary: SessionContinuityLayerSummary, sourceSessionId: string_optional) SessionContinuityState
+replaceSessionContinuityLayerSummary(base: SessionContinuityState, summary: SessionContinuityLayerSummary, sourceSessionId: string_optional) SessionContinuityState
}
SessionContinuityStore --> SessionContinuitySummary : writes
SessionContinuityStore --> SessionContinuityState : reads
SessionContinuityStore --> SessionContinuityAuditEntry : appends
SessionContinuityStore --> ContinuityRecoveryRecord : reads_writes
SessionContinuityStore --> SessionContinuityWriteMode : uses
buildSessionContinuityAuditEntry --> SessionContinuityAuditEntry : creates
buildSessionContinuityAuditEntry --> BuildSessionContinuityAuditEntryOptions : uses
buildContinuityRecoveryRecord --> ContinuityRecoveryRecord : creates
buildContinuityRecoveryRecord --> BuildContinuityRecoveryRecordOptions : uses
session_continuity_layer_ops --> SessionContinuityState : updates
session_continuity_layer_ops --> SessionContinuityLayerSummary : consumes
RolloutEvidence --> RolloutMeta : fields_aligned
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The new
selectRefreshRolloutandpersistSessionContinuityhelpers centralize important behavior; consider adding brief inline comments or JSDoc summarizing their provenance/priority rules and replace-vs-merge semantics, since future changes to continuity behavior will likely need to start from these functions. - When
selectRefreshRolloutfails to find a rollout it throws a generic"No relevant rollout found for this project."; including the requested scope and whether a recovery marker or audit entry was present in the error message would make it much easier for reviewers to diagnose why arefreshfailed.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `selectRefreshRollout` and `persistSessionContinuity` helpers centralize important behavior; consider adding brief inline comments or JSDoc summarizing their provenance/priority rules and replace-vs-merge semantics, since future changes to continuity behavior will likely need to start from these functions.
- When `selectRefreshRollout` fails to find a rollout it throws a generic `"No relevant rollout found for this project."`; including the requested scope and whether a recovery marker or audit entry was present in the error message would make it much easier for reviewers to diagnose why a `refresh` failed.
## Individual Comments
### Comment 1
<location path="docs/architecture.en.md" line_range="55" />
<code_context>
-- each `MEMORY.md` is injected as quoted local data
-- structured topic file refs are appended
-- topic entry bodies are not eagerly loaded
+- each `MEMORY.md` is injected as quoted startup files
+- structured topic file refs are appended as on-demand lookup pointers
+- topic entry bodies are not eagerly loaded at startup
</code_context>
<issue_to_address>
**nitpick (typo):** Tighten singular/plural agreement in this sentence.
Using "each" with plural "files" is grammatically awkward. Consider singular phrasing like "each `MEMORY.md` is injected as a quoted startup file" or "each `MEMORY.md` is injected as quoted startup content."
```suggestion
- each `MEMORY.md` is injected as a quoted startup file
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| - each `MEMORY.md` is injected as quoted local data | ||
| - structured topic file refs are appended | ||
| - topic entry bodies are not eagerly loaded | ||
| - each `MEMORY.md` is injected as quoted startup files |
There was a problem hiding this comment.
nitpick (typo): Tighten singular/plural agreement in this sentence.
Using "each" with plural "files" is grammatically awkward. Consider singular phrasing like "each MEMORY.md is injected as a quoted startup file" or "each MEMORY.md is injected as quoted startup content."
| - each `MEMORY.md` is injected as quoted startup files | |
| - each `MEMORY.md` is injected as a quoted startup file |
There was a problem hiding this comment.
2 issues found across 30 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/lib/domain/rollout.ts">
<violation number="1" location="src/lib/domain/rollout.ts:281">
P2: Variable `seenPrimaryMeta` is set to `true` for any valid session meta, including subagent entries. Either the name is misleading (should be `seenSessionMeta`) or the code is missing a `parsedMeta.isSubagent` guard to skip subagent entries before assigning session fields — consistent with how `findLatestProjectRollout` uses `isPrimaryRolloutMeta` to filter them out.</violation>
</file>
<file name="test/session-continuity.test.ts">
<violation number="1" location="test/session-continuity.test.ts:526">
P2: This assertion checks `positiveBuckets` for "reviewer sub-agent", but that text only exists in the `negativeBuckets` input. The check trivially passes and doesn't actually validate the filtering behavior. It should assert against `negativeBuckets.explicitNextSteps` instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| cwd = await normalizeFsPath(parsedMeta.cwd); | ||
| isSubagent = parsedMeta.isSubagent; | ||
| forkedFromSessionId = parsedMeta.forkedFromSessionId; | ||
| seenPrimaryMeta = true; |
There was a problem hiding this comment.
P2: Variable seenPrimaryMeta is set to true for any valid session meta, including subagent entries. Either the name is misleading (should be seenSessionMeta) or the code is missing a parsedMeta.isSubagent guard to skip subagent entries before assigning session fields — consistent with how findLatestProjectRollout uses isPrimaryRolloutMeta to filter them out.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/lib/domain/rollout.ts, line 281:
<comment>Variable `seenPrimaryMeta` is set to `true` for any valid session meta, including subagent entries. Either the name is misleading (should be `seenSessionMeta`) or the code is missing a `parsedMeta.isSubagent` guard to skip subagent entries before assigning session fields — consistent with how `findLatestProjectRollout` uses `isPrimaryRolloutMeta` to filter them out.</comment>
<file context>
@@ -209,10 +264,21 @@ export async function parseRolloutEvidence(filePath: string): Promise<RolloutEvi
+ cwd = await normalizeFsPath(parsedMeta.cwd);
+ isSubagent = parsedMeta.isSubagent;
+ forkedFromSessionId = parsedMeta.forkedFromSessionId;
+ seenPrimaryMeta = true;
continue;
}
</file context>
| '需要更新测试文件以覆盖新的守卫逻辑' | ||
| ]) | ||
| ); | ||
| expect(positiveBuckets.explicitNextSteps.join("\n")).not.toContain("reviewer sub-agent"); |
There was a problem hiding this comment.
P2: This assertion checks positiveBuckets for "reviewer sub-agent", but that text only exists in the negativeBuckets input. The check trivially passes and doesn't actually validate the filtering behavior. It should assert against negativeBuckets.explicitNextSteps instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/session-continuity.test.ts, line 526:
<comment>This assertion checks `positiveBuckets` for "reviewer sub-agent", but that text only exists in the `negativeBuckets` input. The check trivially passes and doesn't actually validate the filtering behavior. It should assert against `negativeBuckets.explicitNextSteps` instead.</comment>
<file context>
@@ -531,7 +516,39 @@ describe("session continuity domain", () => {
+ '需要更新测试文件以覆盖新的守卫逻辑'
+ ])
+ );
+ expect(positiveBuckets.explicitNextSteps.join("\n")).not.toContain("reviewer sub-agent");
+ });
+
</file context>
| expect(positiveBuckets.explicitNextSteps.join("\n")).not.toContain("reviewer sub-agent"); | |
| expect(negativeBuckets.explicitNextSteps.join("\n")).not.toContain("reviewer sub-agent"); |
Summary
cam session refreshwith replace semantics, provenance selection, and legacy-compatible audit/recovery metadataWhat changed
cam session refreshcommand and keepsave/ wrapper auto-save on merge semanticstriggerandwriteModewhile preserving backward compatibility for older recordssaveandrefreshare not collapsed togethertest/helpers/cam-test-fixtures.tsReview
Verification
pnpm lintpnpm testpnpm buildpnpm exec tsx src/cli.ts session status --jsonpnpm exec tsx src/cli.ts session load --jsonpnpm exec tsx src/cli.ts memory --json --recent 5Notes
.gitignorecoverage was checked and staged diff was scanned for obvious hardcoded secrets before commitSummary by Sourcery
Introduce a replace-oriented
cam session refreshworkflow with stricter continuity provenance selection, enhanced audit and recovery metadata, and clearer reviewer-facing memory/continuity surfaces.New Features:
cam session refreshCLI subcommand and supporting runtime logic to regenerate continuity from selected rollout provenance with replace semantics.Bug Fixes:
Enhancements:
triggerandwriteModefields while keeping legacy entries readable and compatible.cam session status/loadtext and JSON outputs to better explain merged resume behavior, audit previews, and continuity provenance without changing existing core fields.session_metashapes, preserve the first valid identity line, and exclude subagent rollouts when auto-selecting the latest project rollout.Tests:
Summary by cubic
Adds a replace-capable
cam session refreshwith explicit provenance and exact scope handling, and tightens continuity source hygiene across save/refresh and wrapper auto-save. Reviewers get safer, clearer continuity updates, and existing JSON consumers stay compatible.New Features
cam session refresh(replace mode) with--rollout <path>,--scope project|project-local|both, and--json.savestill prefers the latest primary rollout and skips subagents by default.saveand wrapper auto-save remain merge mode; history no longer collapsessaveandrefresh.triggerandwriteModefields; legacy JSON remains readable.Refactors
test/helpers/cam-test-fixtures.ts; added tests for refresh, provenance, legacy compatibility, and Claude-style paths.cam memoryoutput on startup files vs on-demand topic refs.Written for commit 834ed25. Summary will update on new commits.