feat(F160): Cat Journey RPG — collaborative nurturing system - #1
Open
bouillipx wants to merge 1 commit into
Open
feat(F160): Cat Journey RPG — collaborative nurturing system#1bouillipx wants to merge 1 commit into
bouillipx wants to merge 1 commit into
Conversation
Adds the Cat Journey (猫猫足迹) feature: a collaborative nurturing system that records real collaboration events as "footfall" across six dimensions (architecture, review, aesthetics, execution, collaboration, insight). Architecture: - ActivityEventBus: typed in-process event spine (17 event types) - JourneyProjector: event→footfall mapping for cat growth profiles - LeadershipProjector: co-creator leadership dimensions (6 live + 2 shadow) - MemoryProjector: three-tier promotion rules for memory crystallization - OtelBridgeProjector: bridges product facts → F153 OTel instruments Features: - Six-dimension growth radar with level formula: level = floor(sqrt(footfall/100)) - Title/achievement unlock system with rarity tiers - Bond system tracking cat-to-cat interaction depth - Co-creator leadership profile with contribution style detection - Monthly review scheduler for growth reports - PNG export for journey cards Tests: 104 tests / 37 suites / 0 failures covering all projectors and services. Issue: zts212653#480 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bouillipx
pushed a commit
that referenced
this pull request
May 25, 2026
…ed wiring (zts212653#695) * fix(embedding): batch splitting + reprobe + collection embed deps Bug #1: Extract embedIndexedItems from IndexBuilder into shared embed-utils.ts with 64-item batch splitting. IndexBuilder's private version sent all items at once — embed-api MAX_BATCH_SIZE=64 caused >64 items to fail with HTTP 400, silently swallowed by fail-open. Bug #2: Add reprobeIfNeeded() to EmbeddingService. load() probes /health once at startup; if embed-api starts later, isReady() stays false forever. reprobeIfNeeded() re-probes when not ready. Bug #6: Wire embedDeps into CollectionIndexBuilder and library.ts rebuild route. Collection rebuild now produces vector embeddings (was FTS-only). Also pass force param from request body (was ignored). Upstream: zts212653#693 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> [宪宪/Opus-46🐾] * fix(embedding): wire production call sites for reprobeIfNeeded and per-collection VectorStore P1 fixes from gpt55 review: - embed-utils.ts: call reprobeIfNeeded() before isReady() check so all callers (IndexBuilder + CollectionIndexBuilder) get late-start recovery - library.ts: accept embeddingService instead of static embedDeps; dynamically load sqlite-vec and create per-collection VectorStore at rebuild time (prevents cross-DB vector pollution) - index.ts: pass memoryServices.embeddingService to libraryRoutes Tests: add reprobe recovery test, update all embedding mocks with reprobeIfNeeded method. 68/68 embedding tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(embedding): wire setEmbedDeps on collection store for search-time hybrid P1 from gpt55 R2: rebuild writes vectors via CollectionIndexBuilder but the collection's SqliteEvidenceStore never gets setEmbedDeps, so search() stays lexical-only. Now the rebuild route calls store.setEmbedDeps() after constructing the per-collection VectorStore, using the system embedMode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: tianyiliang <tianyiliang@tianyiliangdeMacBook-Pro.local> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Lysander Su <773678591@qq.com>
5 tasks
zts212653
pushed a commit
that referenced
this pull request
Jul 14, 2026
zts212653#1146) * fix(invocation): classify interrupted sessions for self-heal + add QueueProcessor terminal write backstop (zts212653#1145) Two reliability fixes for invocation availability: 1. classifyResumeFailure() now matches "session interrupted" / "already interrupted" / "session terminated" as 'missing_session', triggering session self-heal (drop stale sessionId + retry fresh). Previously, our stall auto-kill (SIGTERM after 7 min idle-silent) caused the CLI daemon to mark sessions "interrupted", but the classifier returned null → no self-heal → cascading resume failures on every subsequent invocation until manual intervention or session TTL expiry. 2. QueueProcessor.executeEntry() finally block now calls ensureTerminalStatus() with RouteChainCompletionTracker, matching messages.ts F194 Z3 parity. If the catch block fails to update the record to 'failed' (e.g. Redis blip), the CAS-guarded backstop ensures the invocation reaches a terminal status instead of staying 'running' and becoming a zombie after 600s grace. The previously silent `catch { /* ignore */ }` now logs a warning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(invocation): raise stall auto-kill from 7 to 15 min (zts212653#1145) 7 minutes was too aggressive for LLM inference — when CLI waits for an API response with large context, it appears idle-silent (no CPU growth, no NDJSON output) even though a legitimate network request is in flight. This caused false stall kills, which interrupted active invocations. 15 min gives ample room for long inference while still catching truly stuck CLIs well before CLI_TIMEOUT_MS (30 min). F194 zombie detection won't false-fire during this window because InvocationTracker slot is held by the live generator. Root cause of: codex sessions getting "interrupted" mid-work. [宪宪/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: update stall threshold assertion to match 15-min change (zts212653#1145) Existing test hardcoded 7*60_000 (420000ms). Updated to match the new 15*60_000 (900000ms) threshold from the prior commit. [宪宪/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(invocation): align stall auto-kill to CLI_TIMEOUT_MS (30 min) (zts212653#1145) The ProcessLivenessProbe cannot distinguish "CLI waiting for LLM API response" from "CLI truly stuck" — both present as idle-silent (no CPU growth, no NDJSON output). Any threshold shorter than CLI_TIMEOUT_MS causes false kills during normal inference. Evidence: thread_mrj033qnuamv9ghe (sol) and thread_mriwjlo1955spve4 (codex) both show repeated "CLI 响应超时 (420s)" kills during active tool-call → API-wait cycles. Codex review invocation was killed after only 16s of work + 7m of waiting for terra API response. Align to 30 min so CLI_TIMEOUT_MS is the single binding idle constraint. [宪宪/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(invocation): address sol review P1/P2/P3 — dynamic stall config, tracker leak, negative tests (zts212653#1145) P1: Replace hardcoded 30-min constant with buildStallAutoKillConfig() that tracks resolved CLI_TIMEOUT_MS. When CLI_TIMEOUT_MS=0 (disabled), stallAutoKill is turned off entirely. Custom values (e.g. 60 min) are respected instead of being capped at 30 min. P2: Move chainTracker.release() out of the .get() guard in QueueProcessor.ts so it always runs — prevents Map leak when InvocationRecordStoreLike lacks .get(). P3: Add negative regex tests (keyboard interrupt, process interrupted, interrupted system call) to verify the interrupt classifier doesn't match non-session interrupt messages. Tests: 118/118 pass (3 new assertions for buildStallAutoKillConfig + 3 negative regex assertions). [宪宪/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(QueueProcessor): add ensureTerminalStatus backstop regression test (zts212653#1145) Addresses codex review P2: the QueueProcessor terminal backstop had no test coverage. New test verifies that when the catch-block update(failed) throws (e.g. Redis blip), ensureTerminalStatus fires a CAS write with expectedStatus=running to ensure the record reaches terminal status. Also exercises the .get() path and confirms the backstop reads the record before attempting the CAS update. [宪宪/claude-opus-4-6🐾] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): backstop test false-green — trigger on patch semantics not call count The zts212653#1145 backstop test was a false green: updateCallCount===1 fired on the userMessageId backfill (call #1), not the catch-block's failed write (call #3). The throw never reached the intended code path, and get() was hardcoded to return 'running' regardless of actual state. Fix: trigger throw on `status==='failed' && !expectedStatus` (catch-block signature) instead of call count. Track record state so get() returns the realistic status after each successful update. Verify the full chain: catch-block throw → record stays running → CAS backstop → terminal failed. Addresses codex review P2 on 37ad884. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
bouillipx
pushed a commit
that referenced
this pull request
Jul 14, 2026
…212653#1103) * fix(F113): include drive-letter layer in directory breadcrumb Windows drive paths (e.g. D:\Projects) were rendering as "此电脑 > Projects" — the drive root segment was silently swallowed by pathToSegments because parts[0] ("D:") was used only as the accumulation seed, never pushed as a clickable segment. The user could not click back to the drive root, and the breadcrumb read wrong. Fix: extract windowsDriveSegments() helper that emits the drive root (D:\, with trailing separator so backend realpath resolves the drive, not cwd-on-drive) as its own segment. Render layer shows the friendly drive label (本地磁盘 (D:)) for any drive-root segment, not just the leaf — so "此电脑 > 本地磁盘 (D:) > Projects" throughout navigation. Unix and under-home paths unchanged. Also adds NEXT_DIST_DIR env to next.config distDir so parallel dev instances can isolate their build cache (default .next unchanged). Tests: +1 frontend case for drive-letter breadcrumb; backend node:test suite 24/24 green (drives endpoint + listAvailableDrives). Co-Authored-By: Claude <noreply@anthropic.com> * fix(F113): show drive letter as "D:" in breadcrumb (VS Code style) co-creator feedback: breadcrumb rendered "此电脑 > D:\ > AI" — the drive segment showed the raw path "D:\" with trailing separator. Expected "D:" without backslash, matching VS Code / common file-picker convention. Root cause: driveRootLabel(seg.path, drives) fell back to the raw absPath ("D:\") whenever the drives list hadn't been lazily loaded yet — which is the common case when a user lands directly on a drive subdirectory without first visiting the "此电脑" drive-picker view. Fix: use seg.label directly for drive segments. windowsDriveSegments already sets label = parts[0] = "D:" (separator stripped), so the drive segment now reads "D:" regardless of whether drives have been fetched. This also drops the dependency on the lazily-loaded drives list for breadcrumb rendering. Removed dead code: isWindowsDriveRoot + driveRootLabel (no longer called after the change). tsc clean, 16/16 tests pass. Co-Authored-By: Claude <noreply@anthropic.com> * fix(F113): address codex review P2 — server-side isWindows gate + hide create-folder in drives view 1. Gate "此电脑" entry on the SERVER's filesystem platform (derived from the browsed path) instead of the browser client's userAgent. A macOS/Linux client browsing a Windows-hosted server now correctly gets drive switching; a Windows client against a non-Windows server no longer shows an empty picker. The browser browses the API server's filesystem, so the gate must reflect server platform, not client UA. 2. Hide the create-folder button in drives view — there is no current directory, so handleCreateDir would post a stale parentPath from the previously browsed directory. tsc clean, 16/16 frontend tests green. Co-Authored-By: Claude <noreply@anthropic.com> * fix(F113): clear create-folder state on entering drives view (R2 review) R2 review (zts212653, CHANGES_REQUESTED): the codex P2 fix only hid the new-folder button in drives view, but an already-open inline create-folder editor survived the transition. handleCreateDir would then post parentPath: browseResult.current from the stale previous directory - the same wrong-location filesystem mutation the earlier review was trying to eliminate. Fix: enterDrivesView now clears create-folder state (creatingDir, newDirName, mkdirError) alongside the existing error/info banner reset, so no inline editor can survive the transition into 此电脑. Regression test: "start create folder -> enter drives view -> no create controls remain" (Red -> Green). tsc clean, 17/17 frontend tests green. Co-Authored-By: Claude <noreply@anthropic.com> * fix(F113): hide stale directory breadcrumb in drives view (R2 P1) R2 follow-up (zts212653): when entering 此电脑 drives view, the old directory breadcrumb segments (e.g. "此电脑 > D: > Projects") kept rendering unconditionally via segments.map(), so the stale previous directory was painted as the current level alongside the drive grid. Fix: gate the breadcrumb segments render on `view !== 'drives'`. The drives view shows only its own "此电脑" root, not the stale path. Regression test extended: "start create folder -> enter drives view" now also asserts the old directory segment ("Projects") is not present in the breadcrumb text (Red -> Green). tsc clean, 17/17 tests green. Co-Authored-By: Claude <noreply@anthropic.com> * chore(F113): revert NEXT_DIST_DIR scope (out of drive picker) Maintainer R2 follow-up: the NEXT_DIST_DIR env (packages/web/next.config.js distDir) and the .gitignore .next-*/ rule were acceptance-infra changes (parallel dev instances for isolated verification), not part of the drive picker feature. Reverted both to upstream/main state to keep this PR scoped to the drive picker only. Co-Authored-By: Claude <noreply@anthropic.com> * fix(F113): injectable drive probe + deterministic tests (R3 P1 #1) Maintainer R3 review: the Windows success path was never actually tested. Windows Smoke CI doesn't run pick-directory.test.js, and on non-Windows CI listAvailableDrives('win32') could return [] because realpathSync throws on every probe - so the test never proved C:/D: discovery works. Fix: listAvailableDrives now accepts an injectable probeRealpath function (defaults to fs.realpathSync). Added 3 deterministic tests using a mock probe: mounted drives (C:/D:) return correct letter/path/label, inaccessible drives are skipped, and the returned path is the resolved real path not the probed root. 27/27 backend tests green. Also fixed: root path construction (`${letter}:\`) was corrupted during an earlier edit (lost the backslash) - now correct. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(F113): extract useDrivesLoader + state machine (R3 P1#2 + P2#4 part 1) Maintainer R3 review P1#2: loading/empty/failure were conflated. drivesLoadedRef was set before the request resolved, so one transient failure permanently disabled retry until remount, and the first render showed "未发现可用磁盘" while the fetch was still pending. Fix: extract useDrivesLoader into its own module (use-drives-loader.ts) with an explicit `idle | loading | ready | error` state machine. The drives view now renders distinct states: "正在加载磁盘列表..." (loading), "磁盘列表加载失败" + retry button (error), "未发现可用磁盘" (ready + empty), or the drive grid (ready + populated). Retry resets to idle, re-triggering the fetch. This also begins P2#4 (split DirectoryBrowser.tsx): the drive loader/state machine is now a focused module. Segment parsing + icons extraction follows. DirectoryBrowser.tsx: 611 -> 566 lines. tsc clean, 17/17 tests green. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(F113): extract segment parsing + icons (R3 P2#4 part 2) Extracted from DirectoryBrowser.tsx: - directory-segments.ts: windowsDriveSegments, pathToSegments, buildBrowseUrl, shouldFallbackToHome + BrowseEntry/BrowseResult/BreadcrumbSegment types - directory-browser-icons.tsx: HomeIcon, PcIcon, DriveIcon, FolderIcon, TerminalIcon DirectoryBrowser.tsx: 583 -> 437 lines. Still above the 350-line hard limit; breadcrumb/drives-view sub-components extraction follows. tsc clean, 17/17 tests green. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(F113): extract DirectoryBreadcrumb component (R3 P2#4 part 3) Extracted the breadcrumb row + new-folder button into DirectoryBreadcrumb component (directory-breadcrumb.tsx), reducing DirectoryBrowser.tsx from 437 to 368 lines. The breadcrumb renders 此电脑 entry + VS Code-style segments + new-folder button, receiving view state + navigation callbacks as props. tsc clean, 17/17 tests green. Co-Authored-By: Claude <noreply@anthropic.com> * refactor(F113): extract DrivesView + meet 350-line limit (R3 P2#4 done) Extracted the drives-picker grid (loading/error/empty/populated states) into DrivesView component (drives-view.tsx). DirectoryBrowser.tsx is now 326 lines, satisfying the 350-line hard limit (down from 611). Full split summary (P2#4): - use-drives-loader.ts: drive loader + idle|loading|ready|error state machine (P1#2) - directory-segments.ts: Windows segment parsing + types - directory-browser-icons.tsx: 5 icons - directory-breadcrumb.tsx: breadcrumb row + new-folder button - drives-view.tsx: drives grid + loading/error/empty states DirectoryBrowser.tsx now focuses on the directory listing + create-folder inline editor + path input, delegating root-navigation concerns to the extracted modules. tsc clean, 17/17 tests green. Co-Authored-By: Claude <noreply@anthropic.com> * feat(F113): server-owned drives capability + path policy filter (R3 P2#3 part 1) Maintainer R3 review P2#3: capability/path policy must come from the server, not UI-inferred path shape (misses UNC roots). Backend changes: - drives endpoint now returns `{ drives, isWindows }` where isWindows is process.platform === 'win32' (server-owned capability, not path heuristic) - drives are filtered by project-path policy (isUnderAllowedRoot) so an allowlist deployment doesn't advertise drive roots that /browse will reject Frontend (part 2, follows): useDrivesLoader will consume isWindows from the drives endpoint instead of inferring from browseResult.current path shape. 27/27 backend tests green. Co-Authored-By: Claude <noreply@anthropic.com> * fix(F113): remove unused imports + biome format (R3 CI fix) CI Lint + Build failed: DirectoryBrowser.tsx had unused imports (BrowseEntry, HomeIcon, PcIcon, DriveIcon) left over after the module extraction, and pick-directory.test.js had a format issue. Fixed by removing unused imports and running biome format --write. tsc clean, 17/17 frontend + 27/27 backend tests green, biome error-level clean. Co-Authored-By: Claude <noreply@anthropic.com> * feat(F113): server-owned capability end-to-end + Windows CI + state tests (R4) R4 review closures: 1. Server capability consumed by frontend (P2#3 end-to-end): browse endpoint returns isWindows: process.platform === 'win32'. DirectoryBrowser uses browseResult.isWindows instead of path-shape regex heuristic (misses UNC roots). 2 regression tests: isWindows=false hides 此电脑, true shows it. 2. Windows CI: pick-directory.test.js wired into windows-smoke.yml. 3. State machine regression: loading (pending->loading->ready) + error+retry tests. Backend isWindows contract assertions for /drives and /browse. tsc clean, 21/21 frontend + 28/28 backend tests green, biome format clean. Co-Authored-By: Claude <noreply@anthropic.com> * test(F113): retry->ready + empty-state regression coverage (R5) R5 review: the error/retry test stopped at asserting the button exists. Extended to exercise the full retry transition (error -> click retry -> idle -> loading -> ready with drive grid) and added an independent empty-state test (ready + [] -> 未发现可用磁盘, not loading/error). useDrivesLoader state transitions now fully covered: - idle -> loading -> ready (loading test) - loading -> error (error test) - error -> retry -> ready (extended retry test) - ready + [] -> 未发现可用磁盘 (empty-state test) 22/22 frontend tests green. Co-Authored-By: Claude <noreply@anthropic.com> * fix: clear stale path in drive picker root Why: Entering the virtual 此电脑 drive picker root is not browsing a concrete project directory. Keeping the previous directory in the manual path field and selected-path footer made the UI look stale and allowed accidental creation against the old path. What: - Clear DirectoryBrowser pathInput when entering drives view. - Notify DirectoryPickerModal when the browser is on a virtual location. - Clear selectedPath in that state so create stays disabled until a real directory is selected. - Add regressions for both the browser input and modal selected-path footer. * fix: fence late cwd selection in drive picker Why: A delayed cwd initializer could treat the virtual 此电脑 root as uninitialized state and reselect a stale directory, enabling thread creation while the drive grid was still visible. [宪宪/gpt-5.5🐾] * fix: surface this pc in directory list Why: Windows users need a discoverable route from any browsed directory back to the drive picker; breadcrumb-only access was too easy to miss, making other disks effectively unreachable from the modal. Verification: pnpm --dir packages/web exec vitest run src/components/ThreadSidebar/__tests__/directory-browser.test.ts; pnpm --dir packages/web exec vitest run src/components/ThreadSidebar/__tests__/directory-picker-modal.test.ts; pnpm --dir packages/web exec tsc --noEmit. [宪宪/gpt-5.5🐾] * refactor: extract this pc list entry Why: R8 flagged DirectoryBrowser over the repository line-count cap after adding the discoverable drive-switching row. Extracting the row presentation keeps the behavior unchanged while returning DirectoryBrowser below the hard cap. Verification: directory-browser.test.ts 23 passed; directory-picker-modal.test.ts 26 passed; tsc --noEmit passed; biome check on touched files exited 0 with pre-existing test warnings only. [宪宪/gpt-5.5🐾] --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Lysander Su <773678591@qq.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Architecture
Features
level = floor(sqrt(footfall / 100))— quadratic curvePhases
Test plan
node --test packages/api/test/journey-projector.test.js— 35 testsnode --test packages/api/test/leadership-projector.test.js— 21 testsnode --test packages/api/test/memory-projector.test.js— 21 testsnode --test packages/api/test/otel-bridge-projector.test.js— 13 testsnode --test packages/api/test/growth-service.test.js— 14 testsIssue: zts212653#480
🤖 Generated with Claude Code