Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [10.7.0] — 2026-02-11 — MEM-ADAPTER: In-Memory Persistence

Adds `InMemoryGraphAdapter`, a zero-I/O implementation of `GraphPersistencePort` for fast tests.

### Added

- **`InMemoryGraphAdapter`** (`src/infrastructure/adapters/InMemoryGraphAdapter.js`): Full in-memory implementation of all five ports (Commit, Blob, Tree, Ref, Config). Uses Git's SHA-1 object format for content-addressable hashing. Accepts optional `author` and `clock` injection for deterministic tests.
- **`adapterValidation.js`** (`src/infrastructure/adapters/adapterValidation.js`): Extracted shared validation functions (`validateOid`, `validateRef`, `validateLimit`, `validateConfigKey`) used by both adapters.
- **Adapter conformance suite** (`test/unit/infrastructure/adapters/AdapterConformance.js`): ~25 shared behavioral tests that run against any `GraphPersistencePort` implementation, ensuring parity between Git and in-memory adapters.
- **`createInMemoryRepo()`** (`test/helpers/warpGraphTestUtils.js`): Test factory for instant in-memory adapter setup — no temp dirs, no git subprocesses.

### Fixed

- **`InMemoryGraphAdapter.writeTree()`**: Rejects malformed mktree entries missing a tab separator instead of silently producing garbage.
- **`InMemoryGraphAdapter._walkLog()`**: Replaced O(n) `queue.shift()` with index-pointer for O(1) dequeue; sort by date descending to match Git's reverse chronological ordering for merge DAGs.
- **`adapterValidation.validateRef()`**: Removed redundant `startsWith('--')` check (already covered by `startsWith('-')`).

### Changed

- **`GitGraphAdapter`**: Validation methods now delegate to shared `adapterValidation.js` functions. No behavioral change.

## [10.6.1] — 2026-02-11 — Code Review Fixups

Addresses code review feedback from PR #23 across SEEKDIFF and SHIELD features.
Expand Down
4 changes: 3 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ All 12 milestones (77 tasks, ~255 human hours, ~13,100 LOC) have been implemente

### M2.T1.MEM-ADAPTER (A-Tier)

- **Status:** `OPEN`
- **Status:** `DONE`

**User Story:** As an architect, I need fast in-memory tests to validate risky logic quickly.

Expand All @@ -162,6 +162,8 @@ All 12 milestones (77 tasks, ~255 human hours, ~13,100 LOC) have been implemente
- parity behaviors with Git adapter (including integrity constraints where relevant)
- shared tests run against both adapters

**Delivered in v10.7.0.**

**Acceptance Criteria:** domain suite passes on memory + git adapters

**Definition of Done:** adapter integrated in test harness; CI includes adapter matrix lane
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@git-stunts/git-warp",
"version": "10.6.1",
"version": "10.7.0",
"description": "Deterministic WARP graph over Git: graph-native storage, traversal, and tooling.",
"type": "module",
"license": "Apache-2.0",
Expand Down
65 changes: 9 additions & 56 deletions src/infrastructure/adapters/GitGraphAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

import { retry } from '@git-stunts/alfred';
import GraphPersistencePort from '../../ports/GraphPersistencePort.js';
import { validateOid, validateRef, validateLimit, validateConfigKey } from './adapterValidation.js';

/**
* Transient Git errors that are safe to retry automatically.
Expand Down Expand Up @@ -374,30 +375,13 @@ export default class GitGraphAdapter extends GraphPersistencePort {

/**
* Validates that a ref is safe to use in git commands.
* Prevents command injection via malicious ref names.
* Delegates to shared validation in adapterValidation.js.
* @param {string} ref - The ref to validate
* @throws {Error} If ref contains invalid characters, is too long, or starts with -/--
* @private
*/
_validateRef(ref) {
if (!ref || typeof ref !== 'string') {
throw new Error('Ref must be a non-empty string');
}
// Prevent buffer overflow attacks with extremely long refs
if (ref.length > 1024) {
throw new Error(`Ref too long: ${ref.length} chars. Maximum is 1024`);
}
// Prevent git option injection (must check before pattern matching)
if (ref.startsWith('-') || ref.startsWith('--')) {
throw new Error(`Invalid ref: ${ref}. Refs cannot start with - or --. See https://github.com/git-stunts/git-warp#security`);
}
// Allow alphanumeric, ., /, -, _ in names
// Allow ancestry operators: ^ or ~ optionally followed by digits
// Allow range operators: .. between names
const validRefPattern = /^[a-zA-Z0-9._/-]+((~\d*|\^\d*|\.\.[a-zA-Z0-9._/-]+)*)$/;
if (!validRefPattern.test(ref)) {
throw new Error(`Invalid ref format: ${ref}. Only alphanumeric characters, ., /, -, _, ^, ~, and range operators are allowed. See https://github.com/git-stunts/git-warp#ref-validation`);
}
validateRef(ref);
}

/**
Expand Down Expand Up @@ -546,42 +530,24 @@ export default class GitGraphAdapter extends GraphPersistencePort {

/**
* Validates that an OID is safe to use in git commands.
* Delegates to shared validation in adapterValidation.js.
* @param {string} oid - The OID to validate
* @throws {Error} If OID is invalid
* @private
*/
_validateOid(oid) {
if (!oid || typeof oid !== 'string') {
throw new Error('OID must be a non-empty string');
}
if (oid.length > 64) {
throw new Error(`OID too long: ${oid.length} chars. Maximum is 64`);
}
const validOidPattern = /^[0-9a-fA-F]{4,64}$/;
if (!validOidPattern.test(oid)) {
throw new Error(`Invalid OID format: ${oid}`);
}
validateOid(oid);
}

/**
* Validates that a limit is a safe positive integer.
* Delegates to shared validation in adapterValidation.js.
* @param {number} limit - The limit to validate
* @throws {Error} If limit is invalid
* @private
*/
_validateLimit(limit) {
if (typeof limit !== 'number' || !Number.isFinite(limit)) {
throw new Error('Limit must be a finite number');
}
if (!Number.isInteger(limit)) {
throw new Error('Limit must be an integer');
}
if (limit <= 0) {
throw new Error('Limit must be a positive integer');
}
if (limit > 10_000_000) {
throw new Error(`Limit too large: ${limit}. Maximum is 10,000,000`);
}
validateLimit(limit);
}

/**
Expand Down Expand Up @@ -721,26 +687,13 @@ export default class GitGraphAdapter extends GraphPersistencePort {

/**
* Validates that a config key is safe to use in git commands.
* Delegates to shared validation in adapterValidation.js.
* @param {string} key - The config key to validate
* @throws {Error} If key is invalid
* @private
*/
_validateConfigKey(key) {
if (!key || typeof key !== 'string') {
throw new Error('Config key must be a non-empty string');
}
if (key.length > 256) {
throw new Error(`Config key too long: ${key.length} chars. Maximum is 256`);
}
// Prevent git option injection
if (key.startsWith('-')) {
throw new Error(`Invalid config key: ${key}. Keys cannot start with -`);
}
// Allow section.subsection.key format
const validKeyPattern = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
if (!validKeyPattern.test(key)) {
throw new Error(`Invalid config key format: ${key}`);
}
validateConfigKey(key);
}

/**
Expand Down
Loading