@gitkraken/shared-tools
v0.3.0
Published
Port interfaces, shared types, and undo-manifest machinery for @gitkraken core-tools.
Readme
@gitkraken/shared-tools
Port interfaces, shared types, and undo-manifest machinery for the core-tools monorepo. Domain packages (like @gitkraken/compose-tools) depend on this package; consumers implement its ports with their own git and AI providers.
Ports
GitPort
Raw git command execution. Throws on non-zero exit code.
interface GitPort {
exec(args: string[], options?: GitExecOptions): Promise<string>;
}
interface GitExecOptions {
env?: Record<string, string>;
stdin?: string;
signal?: AbortSignal;
}All higher-level git operations (diffing, commit building, ref management, rebase orchestration, etc.) are utility functions in domain packages that compose calls to exec(). Consumers never implement higher-level operations on this interface — see ComposeGitPort in @gitkraken/compose-tools for the optional higher-level op surface specific to that domain.
signal is forwarded from every domain workflow that accepts an AbortSignal. Adapters should wire it into their git-process execution so cancellation terminates the process.
AiModelPort
Stateless text generation. Callers pass the full message history each call — session threading is the caller's responsibility, not the model's.
interface AiModelPort {
generate(params: AiGenerateParams): Promise<AiGenerateResult>;
}
interface AiGenerateParams {
system?: string;
messages: AiMessage[];
maxTokens?: number;
temperature?: number;
signal?: AbortSignal;
}
interface AiGenerateResult {
text: string;
usage?: AiTokenUsage;
}signal is forwarded from every AI workflow. Adapters should wire it into the underlying LLM request so in-flight calls cancel.
StoragePort
Async key/value store for a library's own state, already scoped by the host. No TTL; get returns null for an absent key. createMemoryStorage() is a non-durable Map-backed implementation for tests.
interface StoragePort {
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
}TelemetryPort
Fire-and-forget telemetry sink; implementations must not throw. noopTelemetry is the default when the host injects nothing. Callers pass attributes that can hold URLs or error text through redactUrlCredentials.
interface TelemetryPort {
event(name: string, attributes?: TelemetryAttributes): void;
exception(error: unknown, attributes?: TelemetryAttributes): void;
}FileSystemPort
Filesystem access over absolute, host-supplied paths. Absence is null; remove is a no-op when missing and throws on a non-empty directory unless recursive is set; mode is a POSIX permission that non-POSIX hosts ignore.
interface FileSystemPort {
readFile(path: string): Promise<string | null>;
writeFile(path: string, content: string, options?: { mode?: number }): Promise<void>;
remove(path: string, options?: { recursive?: boolean }): Promise<void>;
readDir(path: string): Promise<string[] | null>;
}Implementing the ports
Consumers wrap their own git and AI providers as adapters. Adapters can layer telemetry, caching, error handling, cancellation, priority queuing, or any other behavior — all invisible to the domain packages.
Example: GitPort adapter
import type { GitPort } from '@gitkraken/shared-tools';
const git: GitPort = {
exec: async (args, options) => {
const result = await myGitProvider.run(args, {
cwd: '/path/to/repo',
env: options?.env,
stdin: options?.stdin,
signal: options?.signal,
});
return result.stdout;
},
};Example: AiModelPort adapter
import type { AiModelPort } from '@gitkraken/shared-tools';
const ai: AiModelPort = {
generate: async (params) => {
const response = await myAiProvider.chat(params.messages, {
system: params.system,
maxTokens: params.maxTokens,
temperature: params.temperature,
signal: params.signal,
});
return {
text: response.text,
usage: { inputTokens: response.inputTokens, outputTokens: response.outputTokens },
};
},
};Undo manifests
Domain packages use these primitives to record every git mutation an operation performs and later replay the inverse. The machinery is shared because its shape is general — any operation that moves refs or changes the workdir can build a manifest out of it.
capturePreOperationState(git, options?)
Snapshots the pre-operation state before the workflow touches anything:
- Current HEAD SHA and branch name.
- A detached stash commit of the current workdir (via
git stash create) when the workdir is dirty — stored as a loose object, not added to the stash list.
Undo uses this snapshot to restore HEAD and the workdir. The capture helper is careful not to mutate the real git index when it builds the stash — it uses a temp index via GIT_INDEX_FILE so the consumer's staged state is never perturbed.
UndoStore
Reads and writes UndoManifest objects into the repo's object database under refs/undo/<id>. Manifests survive branch operations and are removed automatically when undo runs. Provides save(manifest), load(id), list(), delete(id).
generateUndoId()
Emits a stable UUID for a new undo manifest.
undoOperation(input) and validateUndo(input)
The core undo execution and dry-run validator. Domain packages (like compose) wrap these with their own input types — undoCompose in compose-tools delegates to undoOperation after translating.
Exports
Values:
createMemoryStoragenoopTelemetryredactUrlCredentialscapturePreOperationStategenerateUndoIdUndoStoreundoOperationvalidateUndo
Types:
GitPort,GitExecOptionsAiModelPort,AiGenerateParams,AiGenerateResult,AiMessage,AiTokenUsageStoragePort,TelemetryPort,TelemetryAttributes,FileSystemPortAiCreditUsage,GetAiCreditUsagePreOperationState,UndoManifest,UndoIdRefKind,RefIdentity,RefSnapshot,RefMovementUndoBlocker,UndoWarning,UndoValidationUndoInput,UndoForceOptions,UndoResult,ValidateUndoInput
Also exported: refKindToFullRefname(kind, name) for resolving a kind-tagged identity to a full refname.
Runtime environment
This package uses Node.js built-ins (node:crypto, node:fs, node:os, node:path) in the undo machinery. Type-only imports are safe in any environment; runtime usage of UndoStore, capturePreOperationState, generateUndoId, undoOperation, validateUndo requires a Node.js runtime.
Contributing
This package follows the monorepo's standard tooling — see the root CONTRIBUTING.md. Local commands:
pnpm --filter @gitkraken/shared-tools build
pnpm --filter @gitkraken/shared-tools typecheck
pnpm --filter @gitkraken/shared-tools testDesign constraints for changes to ports
The ports are intentionally minimal and stable. Adding methods to GitPort or AiModelPort is a breaking change for every consumer — every implementation must be updated. Before proposing a new method:
- Confirm the operation cannot be expressed as a utility function built on top of
exec()orgenerate(). - Confirm the operation is needed by multiple domain packages (not just one).
- Confirm no consumer can reasonably implement it differently in their adapter.
A new port may land ahead of its consumers when a stacked change needs the contract in place before the packages that use it.
In practice, new methods should be rare. The default answer to "should this go in the port?" is no — put it in a domain package as a utility that calls exec() / generate() internally, or in a domain-specific port interface (see ComposeGitPort in compose-tools).