Skip to content

Commit 8886432

Browse files
author
SqlRush
committed
Accept transcript metadata numeric aliases
1 parent c761a31 commit 8886432

5 files changed

Lines changed: 60 additions & 18 deletions

File tree

docs/cc-100-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ M7 补充:Ctrl-S prompt stash 现在保存并恢复 prompt text、cursor 和 p
276276

277277
本轮补充:remote history HTTP `Link` header 分页接受 `previous`/`prev`/`older`/`next` rel URL,并以 body cursor 优先、header cursor fallback 的方式继续抓取。
278278

279+
本轮补充:transcript metadata loader 现在接受 `sessionID``session` 作为 session-scoped metadata ID 别名,并容忍 `prNumber``timeSavedMs``lastSpawnTokens` 等计数字段使用数字字符串。
280+
279281
本轮补充:sidechain/subagent state loader 接受 legacy/fork 命名的 start/finish subtype、ID/type/summary 字段别名和常见状态别名,提升旧 subagent transcript resume/list 的恢复率。
280282

281283
本轮补充:conversation runner 现在会在用户消息入队后计算 compact token warning state,并在触发 warning/error/auto-compact/blocking 阈值时发出 `token_warning` event;warning state 接入 blocking-limit override,auto-compact threshold 判断接入 `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE`,使 runtime warning 和自动压缩使用同一套 window 输入。

docs/claude-code-go-rewrite-plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ test/parity/ # golden tests against TS/official behavior
193193
- 本轮补充:remote history response parser 会递归解包 `data.session.events``data.projectSession.eventConnection``conversation``remoteHistory` 等 GraphQL/session wrapper,继续复用 `nodes`/`edges[].node``pageInfo` pagination 解析。
194194
- 本轮补充:transcript resume 的嵌套 content block 接受 `toolUseId`/`toolUseID``isError``cacheControl``cacheReference` 字段别名,并保留 cache edit 的 `cacheReference`
195195
- 本轮补充:lightweight transcript metadata loader 在 `system`/`compact_boundary` 后清空旧 `marble-origami-commit`/`marble-origami-snapshot` 状态,和 full loader/官方 sessionStorage compact-boundary 语义一致。
196+
- 本轮补充:transcript metadata loader 接受 `sessionID`/`session` 作为 session-scoped metadata ID 别名,并容忍 `prNumber``timeSavedMs``lastSpawnTokens` 等计数字段使用数字字符串。
196197
- 本轮补充:memory 层补齐官方 `memoryAge`/freshness note 语义,`ReadDocumentsWithOptions` 可为超过 1 天的 memory 文档前缀 system-reminder,提示模型把 memory 当作 point-in-time observation 并核对当前代码。
197198
- 本轮补充:Read tool 在 metadata 提供 auto-memory 目录时,会为读取旧 auto-memory 文件的 tool result 前缀 freshness system-reminder,和官方 FileReadTool 的 memory freshness prefix 对齐。
198199
- 本轮补充:memory 层补齐官方 `relevant_memories` attachment 基础,包含 stable memory header、system-reminder 渲染、surfaced path/byte 扫描、按 200 行/4096 bytes 读取并附截断提示的 surfacing reader、mark-after-filter 的 duplicate memory attachment 过滤、最后非 meta user prompt/单词 prompt/60KB session cap 的 prefetch gating、多目录结果排除 read-state/surfaced 后取前 5 个候选的选择逻辑,以及 recent successful tools 窗口收集并排除 pending/failed/同名失败工具。

docs/first-second-parity-audit.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ M6 progress now includes:
145145
- `internal/session`: transcript indexes and session search now recover message `cwd` as project path, accept project/working-directory aliases, and can match sessions by project path.
146146
- `internal/session`: transcript message loading now preserves structured SerializedMessage metadata such as `userType`, `entrypoint`, `version`, and `slug`, including common alias spellings.
147147
- `internal/session`: lightweight transcript metadata loading now clears stale context-collapse commit/snapshot state after compact-boundary messages, matching the full loader and official sessionStorage restore semantics.
148+
- `internal/session`: transcript metadata loading now accepts `sessionID` and `session` as session-scoped ID aliases and tolerates numeric strings for counters such as `prNumber`, `timeSavedMs`, and `lastSpawnTokens`.
148149
- `internal/memory`: memory age/freshness helpers now match official stale-memory guidance, and document loading can prefix old memory files with a system-reminder that they are point-in-time observations.
149150
- `internal/memory`: relevant-memory attachment primitives now match the official `relevant_memories` shape for stable headers, system-reminder rendering, surfaced path/byte scanning, 200-line/4096-byte surfacing reads with truncation notices, mark-after-filter duplicate attachment handling, last-non-meta-user/single-word/session-byte-cap prefetch gating, top-5 candidate filtering after read-state/surfaced de-dup, and recent successful tools collection excluding pending/failed/same-name-failed tools.
150151
- `internal/conversation`: request building now expands `relevant_memories` attachment messages into user/meta system-reminders before Anthropic API normalization.

internal/session/transcript_metadata_fields.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package session
33
import (
44
"bytes"
55
"encoding/json"
6+
"strconv"
7+
"strings"
68

79
"ccgo/internal/contracts"
810
)
@@ -36,7 +38,7 @@ func (f transcriptMetadataFields) idValue(keys ...string) contracts.ID {
3638
}
3739

3840
func (f transcriptMetadataFields) sessionIDValue() contracts.ID {
39-
return f.idValue("sessionId", "session_id", "sessionUuid", "sessionUUID", "session_uuid")
41+
return f.idValue("sessionId", "sessionID", "session_id", "session", "sessionUuid", "sessionUUID", "session_uuid")
4042
}
4143

4244
func (f transcriptMetadataFields) intValue(keys ...string) int {
@@ -49,6 +51,13 @@ func (f transcriptMetadataFields) intValue(keys ...string) int {
4951
if err := json.Unmarshal(raw, &value); err == nil {
5052
return value
5153
}
54+
var text string
55+
if err := json.Unmarshal(raw, &text); err == nil {
56+
value, err := strconv.Atoi(strings.TrimSpace(text))
57+
if err == nil {
58+
return value
59+
}
60+
}
5261
}
5362
return 0
5463
}
@@ -102,25 +111,23 @@ func parseTaskSummaryMetadata(line []byte) (TaskSummaryEntry, bool) {
102111
}
103112

104113
func parsePRLinkMetadata(line []byte) (PRLinkEntry, bool) {
105-
var entry PRLinkEntry
106-
if err := json.Unmarshal(line, &entry); err != nil {
107-
return PRLinkEntry{}, false
108-
}
109114
fields, err := parseTranscriptMetadataFields(line)
110115
if err != nil {
111116
return PRLinkEntry{}, false
112117
}
118+
var entry PRLinkEntry
119+
_ = json.Unmarshal(line, &entry)
113120
if entry.SessionID == "" {
114121
entry.SessionID = fields.sessionIDValue()
115122
}
116123
if entry.PRNumber == 0 {
117-
entry.PRNumber = fields.intValue("pr_number")
124+
entry.PRNumber = fields.intValue("prNumber", "pr_number", "number")
118125
}
119126
if entry.PRURL == "" {
120-
entry.PRURL = fields.stringValue("pr_url")
127+
entry.PRURL = fields.stringValue("prUrl", "prURL", "pr_url", "url")
121128
}
122129
if entry.PRRepository == "" {
123-
entry.PRRepository = fields.stringValue("pr_repository")
130+
entry.PRRepository = fields.stringValue("prRepository", "pr_repository", "repository", "repo")
124131
}
125132
return entry, true
126133
}
@@ -199,16 +206,17 @@ func parseTombstoneMetadata(line []byte) (TombstoneEntry, bool) {
199206
}
200207

201208
func parseSpeculationAcceptMetadata(line []byte) (SpeculationAcceptEntry, bool) {
202-
var entry SpeculationAcceptEntry
203-
if err := json.Unmarshal(line, &entry); err != nil {
204-
return SpeculationAcceptEntry{}, false
205-
}
206209
fields, err := parseTranscriptMetadataFields(line)
207210
if err != nil {
208211
return SpeculationAcceptEntry{}, false
209212
}
213+
var entry SpeculationAcceptEntry
214+
_ = json.Unmarshal(line, &entry)
210215
if entry.TimeSavedMS == 0 {
211-
entry.TimeSavedMS = fields.intValue("time_saved_ms")
216+
entry.TimeSavedMS = fields.intValue("timeSavedMs", "timeSavedMS", "time_saved_ms", "timeSaved", "time_saved")
217+
}
218+
if entry.Timestamp == "" {
219+
entry.Timestamp = fields.stringValue("timestamp", "createdAt", "created_at", "time")
212220
}
213221
return entry, true
214222
}
@@ -244,19 +252,17 @@ func parseContextCollapseCommitMetadata(line []byte) (ContextCollapseCommitEntry
244252
}
245253

246254
func parseContextCollapseSnapshotMetadata(line []byte) (ContextCollapseSnapshotEntry, bool) {
247-
var entry ContextCollapseSnapshotEntry
248-
if err := json.Unmarshal(line, &entry); err != nil {
249-
return ContextCollapseSnapshotEntry{}, false
250-
}
251255
fields, err := parseTranscriptMetadataFields(line)
252256
if err != nil {
253257
return ContextCollapseSnapshotEntry{}, false
254258
}
259+
var entry ContextCollapseSnapshotEntry
260+
_ = json.Unmarshal(line, &entry)
255261
if entry.SessionID == "" {
256262
entry.SessionID = fields.sessionIDValue()
257263
}
258264
if entry.LastSpawnTokens == 0 {
259-
entry.LastSpawnTokens = fields.intValue("last_spawn_tokens")
265+
entry.LastSpawnTokens = fields.intValue("lastSpawnTokens", "last_spawn_tokens")
260266
}
261267
return entry, true
262268
}

internal/session/transcript_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,38 @@ func TestLoadTranscriptCollectsMetadataEntries(t *testing.T) {
689689
}
690690
}
691691

692+
func TestLoadTranscriptMetadataAcceptsSessionIDAndNumericStrings(t *testing.T) {
693+
path := writeTranscript(t, []string{
694+
`{"type":"pr-link","sessionID":"s4","prNumber":"44","prUrl":"https://github.com/o/r/pull/44","prRepository":"o/r"}`,
695+
`{"type":"speculation-accept","createdAt":"2026-01-01T00:00:05Z","timeSavedMs":"5600"}`,
696+
`{"type":"marble-origami-snapshot","sessionID":"s4","armed":true,"lastSpawnTokens":"96"}`,
697+
})
698+
transcript, err := LoadTranscript(path)
699+
if err != nil {
700+
t.Fatal(err)
701+
}
702+
if transcript.PRLinks["s4"].PRNumber != 44 || transcript.PRLinks["s4"].PRRepository != "o/r" {
703+
t.Fatalf("transcript pr link = %#v", transcript.PRLinks)
704+
}
705+
if len(transcript.SpeculationAccepts) != 1 || transcript.SpeculationAccepts[0].TimeSavedMS != 5600 || transcript.SpeculationAccepts[0].Timestamp != "2026-01-01T00:00:05Z" {
706+
t.Fatalf("transcript speculation = %#v", transcript.SpeculationAccepts)
707+
}
708+
if transcript.ContextCollapseSnapshot == nil || transcript.ContextCollapseSnapshot.SessionID != "s4" || transcript.ContextCollapseSnapshot.LastSpawnTokens != 96 {
709+
t.Fatalf("transcript snapshot = %#v", transcript.ContextCollapseSnapshot)
710+
}
711+
712+
metadata, err := LoadTranscriptMetadata(path)
713+
if err != nil {
714+
t.Fatal(err)
715+
}
716+
if metadata.PRLinks["s4"].PRNumber != 44 || metadata.ContextCollapseSnapshot == nil || metadata.ContextCollapseSnapshot.LastSpawnTokens != 96 {
717+
t.Fatalf("metadata = %#v snapshot=%#v", metadata.PRLinks, metadata.ContextCollapseSnapshot)
718+
}
719+
if len(metadata.SpeculationAccepts) != 1 || metadata.SpeculationAccepts[0].TimeSavedMS != 5600 {
720+
t.Fatalf("metadata speculation = %#v", metadata.SpeculationAccepts)
721+
}
722+
}
723+
692724
func TestReappendSessionMetadataWritesSessionScopedTailEntries(t *testing.T) {
693725
path := writeTranscript(t, []string{
694726
`{"type":"custom-title","sessionId":"s1","customTitle":"Title"}`,

0 commit comments

Comments
 (0)