import { ExtensionContext } from "@earendil-works/pi-coding-agent"; //#region src/types/editorConfigService.d.ts /** Read-only: writes go through the shared config writer in `@agimon-ai/doompi-config`. */ interface IEditorConfigService { /** Shared Doom config, where `editor.command` belongs. */ path(): string; /** Package-scoped fallback for standalone installs with no Doom config. */ packagePath(): string; command(): Promise; } //#endregion //#region src/types/domain.d.ts export type FileEditTool = 'edit' | 'write' | 'bash' | 'user'; export type FileEditState = 'modified' | 'added' | 'deleted' | 'unchanged' | 'binary' | 'external'; /** * How a change was discovered, which is what decides whether it can be diffed. * * A `tool` change names its path in the call arguments, so the content is read * before the tool runs and both sides of the change are known. A `scan` change * is found by comparing a tree manifest taken around a bash call: the path is * only known afterwards, so there is no "before" to diff against. */ export type FileEditOrigin = 'tool' | 'scan'; /** * One recorded change, appended to the session's timeline. * * Version 1 events carried the path alone. Version 2 adds the blob hashes that * make a diff possible; both are still read, because a session already running * when the package updates keeps appending to the file it opened. */ export interface TimelineEvent { version: 2; path: string; tool: FileEditTool; at: number; origin: FileEditOrigin; /** Snapshot hash of the content before the change; absent for a scan-found path. */ before?: string; /** Snapshot hash of the content after the change; absent when it could not be stored. */ after?: string; additions?: number; removals?: number; /** * Set on a scan change whose content was proven to have moved, rather than * only its modification time. Absent on a scan recorded by an older build, * which could not tell an edit from a file a command merely touched. */ verified?: boolean; /** * Set on a tool change whose file did not exist when the call began. * * A missing `before` is ambiguous on its own: the file may have been created, * or it may have existed and been too large or too binary to capture. Only * the first has a diff, and it is the whole file against nothing, so the two * are told apart here rather than guessed at by a reader. */ created?: boolean; } /** A version 1 event, still accepted from a timeline opened by an older build. */ export interface LegacyTimelineEvent { version: 1; path: string; tool: 'edit' | 'write' | 'bash'; at: number; } export interface FileEditEntry { path: string; tool: FileEditTool; at: number; count: number; } /** One change in a file's history, as the cockpit lists it. */ export interface FileEditVersion { /** Position in the file's own history, oldest first, starting at 1. */ index: number; tool: FileEditTool; at: number; origin: FileEditOrigin; before?: string; after?: string; additions?: number; removals?: number; verified?: boolean; /** Set when the file did not exist before this change, so its baseline is empty. */ created?: boolean; } export interface FileDiff { path: string; state: FileEditState; lines: string[]; additions: number; removals: number; tracked: boolean; truncated: boolean; suggestedLine: number; } export interface ResolvedEditor { template: string; source: 'configured' | 'VISUAL' | 'EDITOR' | 'fallback'; } //#endregion //#region src/types/editorLauncher.d.ts interface EditorTui { stop(): void; start(): void; requestRender(force?: boolean): void; } interface EditorLaunchResult { success: boolean; error?: string; } interface IEditorLauncher { resolve(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): Promise; launch(filePath: string, line: number, tui: EditorTui): Promise; } //#endregion //#region src/types/editTracker.d.ts interface IEditTracker { start(id: string, tool: string, args: unknown, cwd: string): Promise; /** * Closes out a call. The working directory comes back in because a bash call * is closed by walking the tree, not by re-reading a path the arguments named. */ end(id: string, isError: boolean, cwd: string): Promise; /** * Awaits the tree walk `end` deferred so it does not hold up a tool result. * A caller about to clear this session's state needs it, or the walk appends * behind the clear. */ flush(): Promise; /** * Drops the tree baseline so a new session does not inherit the last one's, * and takes the paths this session's own bookkeeping occupies, which a tree * walk must never report as an edit. * * `isIgnored` carries the project's own ignore rules, so a path the project * disowns is dropped before it costs a git call, a read, or a stored blob. */ reset(options?: { exclude?: readonly string[]; isIgnored?: (filePath: string) => boolean; }): void; } //#endregion //#region src/types/fileEditPaths.d.ts interface IFileEditPaths { sessionKey(sessionId: string, env?: NodeJS.ProcessEnv): string; timelinePath(cwd: string, sessionKey: string): string; /** Where this session's content snapshots live, beside its timeline. */ snapshotsPath(cwd: string, sessionKey: string): string; /** * The directory every session's state shares. A sweep has to read it, and the * paths for one session cannot name it on their own. */ stateDirectory(): string; /** * Where a build before this one kept the same state, or undefined when there * is nowhere. Only a repository answers, because the old layout wrote into * the git common directory and nowhere else. */ legacyStateDirectory(cwd: string): string | undefined; } //#endregion //#region src/types/fileEditWorkflow.d.ts interface IFileEditWorkflow { open(ctx: ExtensionContext): Promise; } //#endregion //#region src/types/gitDiffService.d.ts interface IGitDiffService { diff(cwd: string, filePath: string): Promise; } //#endregion //#region src/types/snapshotStore.d.ts /** * Store and read the file content this session's diffs are built from. * * Nothing else in reach holds a "before". Git is not assumed, because plenty of * working directories are not repositories, and the edit tool discards the * content it read the moment it has written. So the package keeps its own * copies, addressed by the hash of what they hold, which means a file rewritten * with identical content costs nothing the second time. * * Every snapshot is bounded: text only, under a byte cap. A file failing either * test is still recorded as changed, just without content, and the surfaces say * so rather than drawing an empty diff. * * Declared here, not beside its implementation: services may import types but * never adapters, so a port living in src/adapters would be unreachable from * the code that needs it. */ interface SnapshotStorePort { /** Names the directory the snapshots live in; called once per session. */ initialize(directory: string): void; /** * Stores what the file holds right now and answers its hash, or undefined * when the file is missing, binary, or past the cap. */ capture(filePath: string): Promise; /** Stores content already in hand, which is what a save from the cockpit has. */ put(content: string): Promise; /** The content behind a hash, or undefined once the session dropped it. */ read(hash: string): Promise; /** Removes every snapshot this session took. */ clear(): Promise; } //#endregion //#region src/types/timelineStore.d.ts interface ITimelineStore { initialize(filePath: string): void; append(event: TimelineEvent): Promise; list(): Promise; /** One file's history, oldest first; empty when the session never touched it. */ versions(filePath: string): Promise; clear(): Promise; } //#endregion //#region src/types/treeManifest.d.ts /** * Take a bounded manifest of a working tree and report which files moved * between two of them. * * This is what catches the change no tool announced. The agent can write a * script and run it, and the bash call names neither the script's targets nor * a glob that would give them away, so reading the command string can never be * complete. Comparing the tree either side of the call can. * * The walk is bounded on both entries and depth and skips the directories a * build fills, because the point is to notice source changing, not to inventory * a machine. A manifest that hit a cap says so, so a caller can report the * shortfall instead of implying it saw everything. * * Declared here, not beside its implementation: services may import types but * never adapters, so a port living in src/adapters would be unreachable from * the code that needs it. */ interface TreeManifest { /** Absolute path to an opaque fingerprint of the file's size and modification time. */ readonly entries: ReadonlyMap; /** True when the walk hit a cap, so the manifest covers only part of the tree. */ readonly truncated: boolean; } interface TreeManifestPort { /** * Walks the tree once and records what it found. * * `exclude` names absolute files and directories the walk must not descend * into or record. A caller passes its own storage: this package writes a * timeline and a snapshot directory that can land inside the very tree it is * watching, and reporting those as edits would make every recorded change * cause another one. */ take(root: string, exclude?: readonly string[]): Promise; /** * One file's fingerprint, in the same vocabulary a walk records, or undefined * when it is not there. A caller that already handled a file folds this into * its manifest so the next walk does not report the same change twice. */ fingerprint(filePath: string): Promise; /** * When the file was last written, in epoch milliseconds, or undefined when it * is not there. * * A comparison of two manifests only says a file differs between them, not * when it moved, so a path left dirty by earlier work looks the same as one a * command just wrote. Reading the modification time back tells the two apart. */ modifiedAt(filePath: string): Promise; /** Every path added, removed, or modified between two manifests, sorted. */ changed(before: TreeManifest, after: TreeManifest): string[]; /** * Whether two fingerprints disagree about the file's size. * * A fingerprint is opaque to everything outside the adapter that wrote it, so * the size question is asked here rather than by parsing the string at the * call site. Size is the cheap half of the answer to "did the bytes move": a * different size proves they did, while an equal size proves nothing either * way and leaves the caller to compare content. * * Answers false when either side is absent. A path that appeared or vanished * is a creation or a deletion, which the caller already knows from the * manifests themselves and must not route through here. */ sizeChanged(before: string | undefined, after: string | undefined): boolean; } //#endregion //#region src/types/index.d.ts /** Everything the file-edit runtime is assembled from. */ export interface FileEditDependencies { readonly paths: IFileEditPaths; readonly timeline: ITimelineStore; readonly snapshots: SnapshotStorePort; readonly manifests: TreeManifestPort; readonly diffs: IGitDiffService; readonly editorConfig: IEditorConfigService; readonly editTracker: IEditTracker; readonly editorLauncher: IEditorLauncher; readonly workflow: IFileEditWorkflow; } //#endregion //#region src/tui/fileEditDependencies.d.ts /** * Compose the file-edit runtime. * * Construction order is the dependency order, so the graph is readable top to * bottom and a cycle is a compile error rather than a resolution failure at * runtime. Pass overrides to substitute a double in tests. */ export declare function createFileEditDependencies(overrides?: Partial): FileEditDependencies; //#endregion //# sourceMappingURL=index.d.mts.map