Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
49177ba
feat(export): add git mind export command (NEX-006)
Feb 12, 2026
4a52bf0
feat(frontmatter): import from markdown frontmatter (NEX-003)
Feb 12, 2026
3c79244
feat(remote): cross-repo edge protocol (NEX-004)
Feb 12, 2026
eae5050
feat(merge): multi-repo graph merge (NEX-005)
Feb 12, 2026
21988aa
feat(action): GitHub Action for PR suggestions (NEX-001)
Feb 12, 2026
d660153
feat(format-pr): PR review commands and display (NEX-002)
Feb 12, 2026
4a01ab5
chore: bump version to 2.0.0-alpha.3 for NEXUS release
Feb 12, 2026
c7e6fea
fix: address PR #201 review feedback (#195, #196, #197, #198, #199, #…
Feb 12, 2026
eaea22c
fix: address PR #201 round-2 review feedback (#195, #196, #197, #198,…
Feb 13, 2026
eba0277
fix(format-pr): escape backslashes before pipes in table cells (#200)
Feb 13, 2026
c0b0cac
fix: address PR #201 round-3 review feedback (#196, #197, #200)
Feb 13, 2026
b9681ec
feat(epoch): implement time-travel via epoch markers (#202)
Feb 13, 2026
8fe6a7b
fix(epoch): prevent shell injection in git commands (#202)
Feb 13, 2026
7ea6a56
fix(action): add contents:read permission to review workflow (#200)
Feb 13, 2026
5a1db26
fix(action): respect workflow-level GITMIND_AGENT env var (#199)
Feb 13, 2026
7fd1672
fix(format-pr): reject index 0 and strip backticks in table (#200)
Feb 13, 2026
f0f89d4
fix(frontmatter): narrow error catch and fix extension stripping (#196)
Feb 13, 2026
791218b
fix(remote): clear error for non-prefixed IDs in qualifyNodeId (#197)
Feb 13, 2026
22ebfce
chore: add v2.0.0-alpha.4 changelog for epoch + review fixes (#202)
Feb 13, 2026
0baf756
fix: address round-4 review nitpicks (#201, #202)
Feb 13, 2026
0ff3d63
fix: address round-5 review feedback (#195, #196, #197, #200, #202)
Feb 13, 2026
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
8 changes: 8 additions & 0 deletions .github/git-mind.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# git-mind configuration for GitHub Actions
# Copy to .github/git-mind.yml and customize

suggest:
# Agent command for generating suggestions.
# Must accept prompt on stdin and output JSON on stdout.
# Example using Claude:
agent: "claude -p --output-format json"
100 changes: 100 additions & 0 deletions .github/workflows/gitmind-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: git-mind Review Commands

on:
issue_comment:
types: [created]

permissions:
contents: read
pull-requests: write
issues: write
Comment on lines +7 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

contents: read may be insufficient if graph mutations need to persist.

The accept/reject/accept-all commands write to the graph (via git notes), but the workflow never pushes those changes back to the remote. This means the operations are effectively lost after the workflow run completes.

If graph mutations should persist, you need:

  1. contents: write permission
  2. A git push step after the CLI command

If the workflow is only meant to acknowledge the command via a comment reply (and the actual mutation happens elsewhere), this is fine as-is but the CLI invocations are unnecessary overhead.

🤖 Prompt for AI Agents
In @.github/workflows/gitmind-review.yml around lines 7 - 10, The workflow
currently sets permissions: contents: read but the CLI commands that run
(accept, reject, accept-all) mutate the graph via git notes and those changes
are never pushed; change the workflow's permissions to set contents: write and
add a step to push commits (e.g., run git push origin HEAD) after the CLI
invocation that performs graph mutations so the notes/graph updates persist;
alternatively, if you only intend to reply with comments and not persist
mutations, remove the accept/reject/accept-all CLI invocations to avoid
unnecessary work.


jobs:
handle-command:
if: >
github.event.issue.pull_request &&
contains(github.event.comment.body, '/gitmind')
runs-on: ubuntu-latest
steps:
# Checkout default branch (trusted code) — never the PR head.
# issue_comment is a privileged context; checking out attacker-controlled
# code would allow arbitrary execution with write permissions.
- name: Checkout
uses: actions/checkout@v4

- name: Check commenter permissions
id: authz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENTER: ${{ github.event.comment.user.login }}
REPO: ${{ github.repository }}
run: |
PERMISSION=$(gh api "repos/${REPO}/collaborators/${COMMENTER}/permission" --jq '.permission')
if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" && "$PERMISSION" != "maintain" ]]; then
echo "User ${COMMENTER} does not have write access. Skipping."
echo "authorized=false" >> "$GITHUB_OUTPUT"
else
echo "authorized=true" >> "$GITHUB_OUTPUT"
fi

- name: Setup Node.js
if: steps.authz.outputs.authorized == 'true'
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install dependencies
if: steps.authz.outputs.authorized == 'true'
run: npm ci

- name: Parse and execute command
if: steps.authz.outputs.authorized == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMENT_BODY: ${{ github.event.comment.body }}
PR_NUMBER: ${{ github.event.issue.number }}
REPO: ${{ github.repository }}
run: |
# Extract command from comment
COMMAND=$(printf '%s' "$COMMENT_BODY" | grep -oP '/gitmind\s+\K(accept-all|accept\s+\d+|reject\s+\d+)' || true)

if [ -z "$COMMAND" ]; then
echo "No valid /gitmind command found"
exit 0
fi

ACTION=$(echo "$COMMAND" | awk '{print $1}')
INDEX=$(echo "$COMMAND" | awk '{print $2}')

case "$ACTION" in
accept-all)
if node bin/git-mind.js review --batch accept --json; then
REPLY="All pending suggestions accepted."
else
REPLY="Failed to accept suggestions."
fi
;;
accept)
if [ -z "$INDEX" ] || ! [[ "$INDEX" =~ ^[0-9]+$ ]]; then
REPLY="Invalid index. Usage: \`/gitmind accept <number>\`"
elif node bin/git-mind.js review --batch accept --index "$INDEX" --json; then
REPLY="Suggestion $INDEX accepted."
else
REPLY="Failed to accept suggestion $INDEX."
fi
;;
reject)
if [ -z "$INDEX" ] || ! [[ "$INDEX" =~ ^[0-9]+$ ]]; then
REPLY="Invalid index. Usage: \`/gitmind reject <number>\`"
elif node bin/git-mind.js review --batch reject --index "$INDEX" --json; then
REPLY="Suggestion $INDEX rejected."
else
REPLY="Failed to reject suggestion $INDEX."
fi
;;
esac

if [ -n "$REPLY" ]; then
gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \
-f body="**git-mind:** $REPLY"
fi
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,75 @@ 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.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0-alpha.4] - 2026-02-13

### Added

- **`git mind at <ref>` command** — Time-travel: materialize the graph at a historical point via epoch markers. Resolves git refs to Lamport ticks and filters the CRDT graph to that ceiling. Supports `--json` output with epoch metadata (#202)
- **Epoch API** — `getCurrentTick(graph)`, `recordEpoch(graph, sha, tick)`, `lookupEpoch(graph, sha)`, `lookupNearestEpoch(graph, cwd, sha)`, `getEpochForRef(graph, cwd, ref)` in `src/epoch.js` (#202)
- **Automatic epoch recording** — `processCommit` now records an epoch marker after processing each commit, correlating the commit SHA to the current Lamport tick (#202)

### Fixed

- **Shell injection in `src/epoch.js`** — Replaced `execSync` string interpolation with `execFileSync` array args in `lookupNearestEpoch` and `getEpochForRef`, preventing command injection via crafted ref names (#202)
- **Missing `contents` permission in `gitmind-review.yml`** — Workflow now includes `contents: read` so `actions/checkout` can fetch the repo; unspecified scopes default to `none` when `permissions` is explicit (#200)
- **`action.yml` ignores workflow-level `GITMIND_AGENT`** — Validation step no longer overrides inherited env var with empty `inputs.agent`; suggest step falls back to `env.GITMIND_AGENT` (#199)
- **`parseReviewCommand` accepts index 0** — Now returns `null` for index `< 1` since suggestions are 1-indexed (#200)
- **Backtick characters in PR suggestion table** — `formatSuggestionsAsMarkdown` strips backticks from source/target to prevent breaking inline code spans (#200)
- **`findMarkdownFiles` swallows all errors** — Now only catches `ENOENT`/`ENOTDIR`; permission errors and other failures propagate (#196)
- **`extractGraphData` strips extension from directory name** — `String.replace` with `extname` only replaced the first `.md` occurrence; now uses `slice` to target only the trailing extension (#196)
- **`qualifyNodeId` unhelpful error for non-prefixed IDs** — Now throws a descriptive error mentioning `prefix:identifier` format instead of falling through to `buildCrossRepoId`'s generic validation (#197)
- **`qualifyNodeId` accepts multi-colon local IDs** — `a:b:c` now throws a clear error instead of falling through to `buildCrossRepoId`'s generic validation (#197)
- **`formatSuggestionsAsMarkdown` crashes on missing `type`** — All suggestion fields (`source`, `target`, `type`, `confidence`, `rationale`) now have null-coalescing guards (#200)
- **Empty pending list produces confusing range** — `reviewCmd` now says "No pending suggestions to review" instead of "Index N out of range (1-0)" (#200)
- **`import`/`export` positional arg breaks with preceding flags** — Both `git mind import --dry-run file.yaml` and `git mind export --format json file.yaml` now scan for the first non-flag argument (#196, #195)
- **Frontmatter closing delimiter matches `---suffix`** — `parseFrontmatter` now requires `---` followed by newline or EOF (#196)
- **Doctor orphan detection uses hardcoded prefixes** — Now uses `SYSTEM_PREFIXES` from validators plus `decision` instead of hardcoded `startsWith` checks (#201)
- **Epoch SHA truncation too short** — Widened from 8 to 12 characters to reduce birthday-paradox collision risk (#202)
- **`serializeExport` silently falls back to YAML** — Now throws on unsupported format instead of silently defaulting (#195)
- **Empty `catch` in `processCommit`** — Epoch recording errors now logged when `GITMIND_DEBUG` is set (#202)
- **`gitmind-review.yml` `echo` for untrusted input** — Replaced with `printf '%s'` to prevent `-n`/`-e`/backslash misbehavior (#200)

### Changed

- **`epoch` added to `SYSTEM_PREFIXES`** — Epoch markers use the `epoch:` prefix, classified as system. Excluded from export and doctor orphan detection (#202)
- **Permission test skipped under root** — `findMarkdownFiles` chmod test now skips when running as root (CI containers) (#196)
- **Test count** — 312 tests across 19 files (was 286 across 18)

## [2.0.0-alpha.3] - 2026-02-12

### Added

- **`git mind export` command** — Serialize the graph to YAML or JSON in v1 import-compatible format, enabling round-trip workflows. Supports `--format yaml|json`, `--prefix <prefix>` filtering, file output or stdout, and `--json` for structured metadata (#195)
- **Export API** — `exportGraph(graph, opts)`, `serializeExport(data, format)`, `exportToFile(graph, path, opts)` in `src/export.js` (#195)
- **`git mind import --from-markdown` command** — Import nodes and edges from markdown file frontmatter. Auto-generates `doc:` IDs from file paths, recognizes all 8 edge types as frontmatter fields. Supports `--dry-run`, `--json`, glob patterns (#196)
- **Frontmatter API** — `parseFrontmatter(content)`, `extractGraphData(path, frontmatter)`, `findMarkdownFiles(basePath, pattern)`, `importFromMarkdown(graph, cwd, pattern, opts)` in `src/frontmatter.js` (#196)
- **`importData` shared pipeline** — Extracted from `importFile` in `src/import.js` for reuse by frontmatter import and future merge (#196)
- **Cross-repo edge protocol** — `repo:owner/name:prefix:identifier` syntax for referencing nodes in other repositories. `git mind link --remote <owner/name>` qualifies local IDs. Validators accept cross-repo format, `extractPrefix` returns inner prefix (#197)
- **Remote API** — `parseCrossRepoId`, `buildCrossRepoId`, `isCrossRepoId`, `extractRepo`, `qualifyNodeId` in `src/remote.js` (#197)
- **`git mind merge` command** — Merge another repository's graph into the local graph with cross-repo qualification. Supports `--from <path>`, `--repo-name <owner/name>`, `--dry-run`, `--json`. Auto-detects repo identifier from origin remote (#198)
- **Merge API** — `mergeFromRepo(localGraph, remotePath, opts)`, `detectRepoIdentifier(repoPath)` in `src/merge.js` (#198)
- **GitHub Action** — Composite action (`action.yml`) that runs `git mind suggest` on PRs and posts formatted suggestions as a comment. Configurable agent command via action input or `.github/git-mind.yml` (#199)
- **PR suggestion display** — `formatSuggestionsAsMarkdown` renders suggestions as a markdown table with `/gitmind accept|reject|accept-all` commands. `parseReviewCommand` parses slash commands from comment bodies (#200)
- **Slash command workflow** — `.github/workflows/gitmind-review.yml` handles `/gitmind accept N`, `/gitmind reject N`, and `/gitmind accept-all` commands in PR comments (#200)

### Fixed

- **Privileged workflow checkout** — `gitmind-review.yml` now checks out the default branch (trusted code) instead of the PR head ref, preventing untrusted code execution in `issue_comment` context. Permissions scoped to `pull-requests: write` and `issues: write` only (#200)
- **Shell injection in `post-comment.js`** — Comment body passed via stdin (`--input -`) instead of shell interpolation, preventing backtick command substitution. Repo and PR number validated before use (#199)
- **`BOOLEAN_FLAGS` missing `dry-run` and `validate`** — `parseFlags` now treats `--dry-run` and `--validate` as boolean flags instead of consuming the next argument as their value (#195, #198)
- **Pipe characters in markdown table** — `formatSuggestionsAsMarkdown` escapes `|` in rationale and type fields to prevent table row corruption (#200)
- **Frontmatter CRLF handling** — `parseFrontmatter` now finds the first newline dynamically instead of assuming `\n` at offset 4, supporting Windows line endings (#196)
- **`buildCrossRepoId` validation** — Throws on malformed `localId` missing `prefix:identifier` format instead of producing an invalid cross-repo ID (#197)
- **Orphaned JSDoc** — `formatExportResult` moved above `formatImportResult`'s JSDoc block to restore correct documentation association (#195)
- **Accept/reject workflow stubs** — Individual `/gitmind accept N` and `/gitmind reject N` now respond with "not yet supported" instead of silently appearing to succeed (#200)
- **`action.yml` stderr mixing** — Suggest step redirects stderr to `/dev/null` instead of mixing it into JSON output (#199)

### Changed

- **`repo` added to `SYSTEM_PREFIXES`** — Cross-repo IDs use the `repo:` prefix, now classified as system (#197)
- **Test count** — 286 tests across 18 files (was 208 across 13)

## [2.0.0-alpha.2] - 2026-02-11

### Added
Expand Down Expand Up @@ -134,5 +203,6 @@ Complete rewrite from C23 to Node.js on `@git-stunts/git-warp`.
- Docker-based CI/CD
- All C-specific documentation

[2.0.0-alpha.3]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.3
[2.0.0-alpha.2]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.2
[2.0.0-alpha.0]: https://github.com/neuroglyph/git-mind/releases/tag/v2.0.0-alpha.0
10 changes: 8 additions & 2 deletions GRAPH_SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,22 @@ The `/` does not require escaping outside JS regex literals.
| Whitespace | Invalid anywhere in the ID — no trimming, no normalization |
| Comparison | Exact byte/character match — no Unicode normalization |

### Reserved: Cross-Repo IDs (v2)
### Cross-Repo IDs

The following syntax is **reserved** for version 2. Version 1 parsers must not accept it.
Cross-repo IDs reference nodes in other repositories.

```
cross-repo-id = "repo:" owner "/" name ":" prefix ":" identifier
```

Example: `repo:neuroglyph/echo:crate:echo-core`

**Rules:**
- The `repo:` prefix is a system prefix — it cannot be used for regular nodes
- `extractPrefix` returns the **inner** prefix (e.g., `crate` for `repo:owner/name:crate:id`)
- Cross-repo IDs are valid in any context where a node ID is accepted
- Use `git mind link --remote <owner/name>` to qualify local IDs as cross-repo

---

## 3. Prefix Taxonomy
Expand Down
107 changes: 99 additions & 8 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ Everything you need to know — from zero to power user.
6. [Views](#views)
7. [Importing graphs from YAML](#importing-graphs-from-yaml)
8. [Commit directives](#commit-directives)
9. [Using git-mind as a library](#using-git-mind-as-a-library)
10. [Appendix A: How it works under the hood](#appendix-a-how-it-works-under-the-hood)
11. [Appendix B: Edge types reference](#appendix-b-edge-types-reference)
9. [Time-travel with `git mind at`](#time-travel-with-git-mind-at)
10. [Using git-mind as a library](#using-git-mind-as-a-library)
11. [Appendix A: How it works under the hood](#appendix-a-how-it-works-under-the-hood)
12. [Appendix B: Edge types reference](#appendix-b-edge-types-reference)

---

Expand All @@ -32,7 +33,7 @@ git-mind captures those relationships explicitly, so you can query them, visuali

**What makes it different?**

- **Git-native** — Your graph is versioned alongside your code. Check out an old commit, get the old graph.
- **Git-native** — Your graph is versioned alongside your code. Use `git mind at` to see the graph at any historical point.
- **Conflict-free** — Built on CRDTs, so multiple people can add edges simultaneously without conflicts.
- **Branch and merge** — Try experimental connections in a branch, merge what works.
- **No setup** — No database to run. No config files. Just `git mind init`.
Expand Down Expand Up @@ -298,17 +299,43 @@ git mind view roadmap # render the roadmap view
git mind view architecture # render the architecture view
```

### `git mind at <ref>`

Show the graph at a historical point in time.

```bash
git mind at HEAD~50
git mind at v1.0.0
git mind at abc123 --json
```

Resolves the ref to a commit SHA, finds the epoch marker (or nearest ancestor), and materializes the graph at that Lamport tick. See [Time-travel with `git mind at`](#time-travel-with-git-mind-at) for details.

**Flags:**

| Flag | Description |
|------|-------------|
| `--json` | Output as JSON (includes epoch metadata) |

### `git mind suggest`

*(Stub — not yet implemented)*
Generate AI-powered edge suggestions based on recent code changes.

Will use AI to suggest edges based on code analysis.
```bash
git mind suggest
git mind suggest --agent "my-agent-cmd" --context HEAD~5..HEAD --json
```

### `git mind review`

*(Stub — not yet implemented)*
Review pending AI suggestions interactively or in batch.

Will present suggested edges for human review and approval.
```bash
git mind review # interactive mode
git mind review --batch accept # accept all pending
git mind review --batch reject # reject all pending
git mind review --json # list pending as JSON
```

### `git mind help`

Expand Down Expand Up @@ -468,6 +495,70 @@ await processCommit(graph, {

---

## Time-travel with `git mind at`

git-mind records **epoch markers** as you commit. Each epoch correlates a git commit SHA to a Lamport tick in the CRDT graph, allowing you to materialize the graph at any historical point in time.

### Setup

Epoch markers are recorded automatically when you use `git mind process-commit` (either manually or via the post-commit hook). Install the hook to start recording:

```bash
git mind install-hooks
```

### Usage

```bash
# See the graph as it was at a specific commit
git mind at HEAD~50

# Use any git ref — branch names, tags, SHAs
git mind at v1.0.0
git mind at abc123

# JSON output (includes epoch metadata)
git mind at HEAD~10 --json
```

### How it works

When a commit is processed, git-mind:

1. Records the current Lamport tick (the CRDT's logical clock)
2. Stores an `epoch:<sha8>` node in the graph with the tick, full SHA, and timestamp
3. These epoch nodes travel with the graph on push/pull/merge — they're part of the CRDT

When you run `git mind at <ref>`:

1. The ref is resolved to a commit SHA
2. The epoch node for that SHA is looked up (or the nearest ancestor's epoch)
3. The graph is materialized with a ceiling at that tick — only patches with `lamport <= tick` are visible
4. You see the graph exactly as it was at that moment in time

### Programmatic usage

```javascript
import { loadGraph, getEpochForRef, computeStatus, getCurrentTick, recordEpoch } from '@neuroglyph/git-mind';

const graph = await loadGraph('.');

// Record an epoch for the current commit
const tick = await getCurrentTick(graph);
await recordEpoch(graph, commitSha, tick);

// Time-travel to a ref
const result = await getEpochForRef(graph, '.', 'HEAD~10');
if (result) {
graph._seekCeiling = result.epoch.tick;
await graph.materialize({ ceiling: result.epoch.tick });
const status = await computeStatus(graph);
console.log(status);
}
```

---

## Using git-mind as a library

git-mind exports its core modules for use in scripts and integrations.
Expand Down
Loading