Skip to content

Commit f532e06

Browse files
committed
検索後も既存cursorの変更履歴を保持する
1 parent 87ed647 commit f532e06

6 files changed

Lines changed: 53 additions & 8 deletions

File tree

‎Sources/AIShellCore/FactoryDiagnostics.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
public enum AIShellProduct {
44
public static let identifier = "aishell"
5-
public static let version = "0.7.3"
5+
public static let version = "0.7.4"
66
public static let diagnosticsSchemaVersion = "aishell.native_factory_diagnostics.v1"
77
public static let runtimeSchemaVersion = "aishell.runtime_configuration.v3"
88
public static let mcpProtocolVersion = "2025-11-25"

‎Sources/AIShellCore/WorkspaceStateRuntime.swift‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,10 +340,16 @@ public actor WorkspaceStateRuntime {
340340
needsPersistence = true
341341
}
342342
if !state.journal.events.isEmpty {
343-
let appliedSequence = state.journal.sequence
344-
_ = try reconcile(paths: state.journal.events.map(\.path), state: &state)
345-
state.journal.discardEvents(through: appliedSequence)
346-
state.knownChangesBySequence.removeAll(keepingCapacity: true)
343+
let retained = state.journal.events
344+
let changes = try reconcile(paths: retained.map(\.path), state: &state)
345+
// 検索開始は他consumerのackではない。照合済み差分もcursorの保持区間に残す。
346+
for change in changes {
347+
for event in retained where
348+
Self.relativePath(event.path, root: root.path) == change.path
349+
|| Self.relativePath(event.path, root: root.path) == change.previousPath {
350+
state.knownChangesBySequence[event.sequence] = change
351+
}
352+
}
347353
needsPersistence = true
348354
}
349355
state.prefetchedPaths.removeAll(keepingCapacity: true)

‎Tests/AIShellCoreTests/WorkspaceStateRuntimeTests.swift‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,33 @@ final class WorkspaceStateRuntimeTests: XCTestCase {
6161
XCTAssertEqual(changedScanCount, 1)
6262
}
6363

64+
func testSearchCursorPreservesEarlierConsumerAcrossNoOpAndChangedEvents() async throws {
65+
let fixture = try TemporaryFixture()
66+
defer { fixture.cleanup() }
67+
let root = fixture.base.appendingPathComponent("workspace", isDirectory: true)
68+
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
69+
let file = root.appendingPathComponent("State.swift")
70+
try "let value = 1\n".write(to: file, atomically: false, encoding: .utf8)
71+
let store = RuntimeStore(baseDirectory: fixture.base.appendingPathComponent("runtime"))
72+
await store.setWorkingDirectoryForTesting(root)
73+
let runtime = WorkspaceStateRuntime(runtimeStore: store, startsFSEvents: false)
74+
let initial = try await runtime.snapshot(path: root.path, contextBudget: 0)
75+
76+
await runtime.ingestObservedPaths([file.path])
77+
_ = try await runtime.currentSearchCursor(path: root.path)
78+
let unchanged = try await runtime.snapshot(path: root.path, sinceCursor: initial.cursor)
79+
XCTAssertTrue(unchanged.changes.isEmpty)
80+
81+
try "let value = 2\n".write(to: file, atomically: false, encoding: .utf8)
82+
await runtime.ingestObservedPaths([file.path])
83+
_ = try await runtime.currentSearchCursor(path: root.path)
84+
_ = try await runtime.currentSearchCursor(path: root.path)
85+
let changed = try await runtime.snapshot(path: root.path, sinceCursor: initial.cursor)
86+
XCTAssertEqual(changed.changes.map(\.path), ["State.swift"])
87+
let repeated = try await runtime.snapshot(path: root.path, sinceCursor: initial.cursor)
88+
XCTAssertEqual(repeated.changes, changed.changes)
89+
}
90+
6491
func testCurrentSearchCursorRestoresCheckpointWithoutFilesystemScan() async throws {
6592
let fixture = try TemporaryFixture()
6693
defer { fixture.cleanup() }
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# 0.7.4
2+
3+
## 修正
4+
5+
- snapshot後にfilesystem通知を受けてからsearch_contextを使うと、検索開始処理が保持中の変更履歴を破棄し、先のcursorをCURSOR_EXPIREDにしていた問題を修正した。
6+
- 検索後も変更履歴と照合結果を保持する。実変更がない通知だけなら元のcursorで編集を続けられ、実変更が残る場合はWORKSPACE_CHANGEDで拒否する。
7+
- 管理UI・Keychain認証の廃止と工場向け診断schemaは維持する。
8+
9+
## 検証
10+
11+
- 修正前にcursor失効を再現するfocused testが失敗し、修正後に成功した。workspace状態の関連テストも成功した。
12+
- 隔離repoの公開MCP呼出しでsnapshot、読取り、検索、4ファイル編集を確認した。別processのfactory診断を挟んだ継続編集も成功した。

‎package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@quolu/aishell",
3-
"version": "0.7.3",
3+
"version": "0.7.4",
44
"description": "A native macOS state runtime with high-density MCP tools for AI development",
55
"license": "Apache-2.0",
66
"author": {

0 commit comments

Comments
 (0)