Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cfd4eaf
feat: move session memory to Redis hot tier
vicary Mar 14, 2026
b9ca275
feat!: mark context overhaul as 0.2.0 release
vicary Mar 14, 2026
64a0bc8
feat: add local session MCP runtime and tool routing
vicary Mar 22, 2026
545952e
fix: report session doctor cache health
vicary Mar 22, 2026
127b159
chore: ignore local worktree directory
vicary Mar 22, 2026
ee10d29
fix: address review follow-up in drain and connection manager
vicary Mar 23, 2026
e4becc6
feat: close narrowed MCP batch and index parity gaps
vicary Mar 23, 2026
1c718c1
merge: integrate narrowed MCP gap closure
vicary Mar 23, 2026
a3c9bd2
test: fix external path fixture after merge
vicary Mar 23, 2026
a1b5bc3
fix: batch chat event writes and trim config prefixes
vicary Mar 23, 2026
f5aceba
fix: restore dnt build compatibility
vicary Mar 23, 2026
c8c089c
fix: address remaining review follow-ups
vicary Mar 23, 2026
8d4f355
fix: keep review follow-ups green in full validation
vicary Mar 23, 2026
b40f593
fix: fail soft when drain claim release errors
vicary Mar 23, 2026
fdf8b85
fix: harden injected memory and redact endpoints
vicary Mar 23, 2026
d821ed7
fix: redact invalid graphiti endpoint credentials
vicary Mar 23, 2026
57c221a
fix: address remaining review follow-ups
vicary Mar 23, 2026
b2bbda1
refactor: share endpoint redaction helpers
vicary Mar 24, 2026
6bd5dcc
fix: preserve session tool root ids after routing
vicary Mar 24, 2026
208bfac
fix: align no-tag version baselines
vicary Mar 24, 2026
87cce93
fix: abort timed out graphiti requests
vicary Mar 24, 2026
2e9a5b0
docs: clarify local build version fallback
vicary Mar 24, 2026
39fa3fc
fix: tighten follow-up review edge cases
vicary Mar 24, 2026
080030d
fix: clarify config and tool denial behavior
vicary Mar 24, 2026
0ae3086
fix: sweep recurring review issues repo-wide
vicary Mar 24, 2026
f32e0b9
fix: address remaining review follow-ups
vicary Mar 24, 2026
1abe4a9
fix: close follow-up review gaps
vicary Mar 24, 2026
5ad04b7
fix: keep config errors dnt-compatible
vicary Mar 24, 2026
4060cc6
fix: close runtime and denial review gaps
vicary Mar 24, 2026
781f334
fix: close review follow-up edge cases
vicary Mar 24, 2026
9fe46a4
docs: clarify review query and root exports
vicary Mar 24, 2026
1c38b70
fix: close remaining review follow-ups
vicary Mar 24, 2026
ab75451
fix: isolate drain retry batch keys
vicary Mar 24, 2026
1163797
fix: harden connection manager request cleanup
vicary Mar 24, 2026
4c333b4
build: restore dnt task
vicary Mar 24, 2026
309012c
refactor: centralize connection controller cleanup
vicary Mar 24, 2026
57649cc
fix: harden config and drain validation
vicary Mar 24, 2026
da8afe9
fix: harden retry alias canonicalization and endpoint coercion
vicary Mar 24, 2026
497b626
fix: harden prompt memory review follow-ups
vicary Mar 24, 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
406 changes: 405 additions & 1 deletion .github/scripts/version.test.ts

Large diffs are not rendered by default.

187 changes: 145 additions & 42 deletions .github/scripts/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* COMMIT_SHA - override for GITHUB_SHA (e.g. PR head SHA)
*/

import { parse as parseJsonc } from "jsr:@std/jsonc@^1.0.2";

/** Semantic version bump type. */
export type Bump = "major" | "minor" | "patch" | "none";

Expand All @@ -18,16 +20,108 @@ export type VersionResult =
| { skip: true }
| { skip: false; version: string; tag: "latest" | "canary" };

export interface VersionCliDeps {
cmd: (...command: string[]) => Promise<string>;
readTextFile: (filePath: string) => Promise<string>;
envGet: (name: string) => string | undefined;
appendFile: (filePath: string, text: string) => void;
log: (message: string) => void;
now: () => Date;
}

export interface CommandOutputResult {
stdout: Uint8Array;
stderr: Uint8Array;
success: boolean;
code: number;
}

export function parseCommandOutput(
command: string[],
result: CommandOutputResult,
): string {
const stdoutText = new TextDecoder().decode(result.stdout).trim();
const stderrText = new TextDecoder().decode(result.stderr).trim();

if (!result.success) {
const stderrSuffix = stderrText ? `: ${stderrText}` : "";
throw new Error(
`Command failed with exit code ${result.code} (${
command.join(" ")
})${stderrSuffix}`,
);
}

return stdoutText;
}

export async function runCommand(...command: string[]): Promise<string> {
const proc = new Deno.Command(command[0], {
args: command.slice(1),
stdout: "piped",
stderr: "piped",
});
return parseCommandOutput(command, await proc.output());
}

function parsePackageManifest(text: string, filePath: string): unknown {
if (filePath.endsWith(".jsonc")) {
return parseJsonc(text);
}

return JSON.parse(text);
}
Comment thread
vicary marked this conversation as resolved.

function getPackageNameFromManifest(manifest: unknown): string | undefined {
if (
manifest &&
typeof manifest === "object" &&
"name" in manifest &&
typeof manifest.name === "string"
) {
return manifest.name;
}

return undefined;
}

const defaultVersionCliDeps: VersionCliDeps = {
cmd: (...command: string[]) => runCommand(...command),
readTextFile: (filePath) => Deno.readTextFile(filePath),
envGet: (name) => Deno.env.get(name),
appendFile: (filePath, text) => {
Deno.writeTextFileSync(filePath, text, { append: true });
},
log: (message) => console.log(message),
now: () => new Date(),
};

/**
* Returns true when any commit body contains a semantic-release style breaking
* change footer/header such as `BREAKING CHANGE: details`.
*/
export function hasBreakingChangeBody(bodies: string[]): boolean {
return bodies.some((body) => /^BREAKING CHANGE:/im.test(body));
}

/**
* Analyze conventional commit subjects and return the highest bump type.
* Analyze conventional commits and return the highest bump type.
*
* Supported formats:
* - `feat: add feature` -> minor
* - `fix: resolve bug` / `perf: speed up path` -> patch
* - `feat!: breaking api change` / `fix!: breaking bugfix` -> major
* - `BREAKING CHANGE: explanation` in a commit body -> major
* - `Release-As: x.y.z` is handled separately as an exact override
*
* Rules:
* - `BREAKING CHANGE` in body or `type!:` → major
* - `feat:` → minor
* - `fix:` / `perf:` → patch
* - Anything else → none
* In `0.x`, a major bump resolves to the next minor version.
*/
export function analyzeCommits(subjects: string[]): Bump {
export function analyzeCommits(
subjects: string[],
bodies: string[] = [],
): Bump {
if (hasBreakingChangeBody(bodies)) return "major";

let bump: Bump = "none";

for (const msg of subjects) {
Expand Down Expand Up @@ -111,6 +205,17 @@ export function hasNonTestChanges(changedFiles: string[]): boolean {
return changedFiles.some((file) => file && !file.endsWith(".test.ts"));
}

/** Parse newline-separated changed-file output into a stable unique list. */
export function parseChangedFiles(output: string): string[] {
return [
...new Set(
output.split("\n").map((line) => line.trim()).filter(
Boolean,
),
),
];
}

/**
* Calculate the next version given all inputs.
*
Expand All @@ -121,7 +226,7 @@ export function calculateVersion(opts: {
currentVersion: string;
/** Conventional commit subjects since last release. */
subjects: string[];
/** Commit bodies (for Release-As detection). */
/** Commit bodies (for Release-As and BREAKING CHANGE detection). */
bodies: string[];
/** Whether this is a "push" (release) or "pull_request" (canary). */
eventName: "push" | "pull_request";
Expand Down Expand Up @@ -151,8 +256,8 @@ export function calculateVersion(opts: {
return { skip: false, version, tag } as const;
}

// Analyze commits
let bump = analyzeCommits(opts.subjects);
// Analyze commits using subjects plus semantic-release style body footers.
let bump = analyzeCommits(opts.subjects, opts.bodies);

// When no git tags, default to patch bump from npm baseline
if (opts.noGitTags && bump === "none") {
Expand Down Expand Up @@ -185,45 +290,40 @@ export function calculateVersion(opts: {
// CLI entry point
// ---------------------------------------------------------------------------

async function run(args: string[]): Promise<void> {
const cmd = async (...command: string[]): Promise<string> => {
const proc = new Deno.Command(command[0], {
args: command.slice(1),
stdout: "piped",
stderr: "piped",
});
const { stdout } = await proc.output();
return new TextDecoder().decode(stdout).trim();
};

export async function run(
args: string[],
deps: VersionCliDeps = defaultVersionCliDeps,
): Promise<void> {
const { cmd, readTextFile, envGet, appendFile, log, now } = deps;
const output = (key: string, value: string): void => {
const ghOutput = Deno.env.get("GITHUB_OUTPUT");
const ghOutput = envGet("GITHUB_OUTPUT");
if (ghOutput) {
Deno.writeTextFileSync(ghOutput, `${key}=${value}\n`, { append: true });
appendFile(ghOutput, `${key}=${value}\n`);
}
console.log(`${key}=${value}`);
log(`${key}=${value}`);
};

// Read package name from deno.json or package.json
let packageName = "unknown";
for (const file of ["deno.json", "deno.jsonc", "package.json"]) {
try {
const text = await Deno.readTextFile(file);
const json = JSON.parse(text);
if (json.name) {
packageName = json.name;
const text = await readTextFile(file);
const manifest = parsePackageManifest(text, file);
const manifestPackageName = getPackageNameFromManifest(manifest);
if (manifestPackageName) {
packageName = manifestPackageName;
break;
}
} catch {
continue;
}
}

const eventName = (Deno.env.get("GITHUB_EVENT_NAME") ?? args[0] ?? "push") as
const eventName = (envGet("GITHUB_EVENT_NAME") ?? args[0] ?? "push") as
| "push"
| "pull_request";
const commitSha = Deno.env.get("COMMIT_SHA") ??
Deno.env.get("GITHUB_SHA") ??
const commitSha = envGet("COMMIT_SHA") ??
envGet("GITHUB_SHA") ??
args[1] ??
await cmd("git", "rev-parse", "HEAD");

Expand All @@ -250,8 +350,9 @@ async function run(args: string[]): Promise<void> {
currentVersion = npmVersion || "0.0.0";
subjects = (await cmd("git", "log", "--format=%s")).split("\n");
bodies = (await cmd("git", "log", "--format=%b")).split("\n");
changedFiles = (await cmd("git", "ls-tree", "-r", "--name-only", "HEAD"))
.split("\n");
changedFiles = parseChangedFiles(
await cmd("git", "log", "--format=", "--name-only"),
);
Comment thread
vicary marked this conversation as resolved.
Comment thread
vicary marked this conversation as resolved.
noGitTags = true;
} else {
currentVersion = latestTag.replace(/^v/, "");
Expand All @@ -267,16 +368,18 @@ async function run(args: string[]): Promise<void> {
`${latestTag}..HEAD`,
"--format=%b",
)).split("\n");
changedFiles = (await cmd(
"git",
"diff",
"--name-only",
`${latestTag}..HEAD`,
)).split("\n");
changedFiles = parseChangedFiles(
await cmd(
"git",
"diff",
"--name-only",
`${latestTag}..HEAD`,
),
);
noGitTags = false;
}

const timestamp = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14);
const timestamp = now().toISOString().replace(/[-:T]/g, "").slice(0, 14);

const result = calculateVersion({
currentVersion,
Expand All @@ -291,13 +394,13 @@ async function run(args: string[]): Promise<void> {

if (result.skip) {
output("skip", "true");
console.log(
log(
`No release-triggering commits since ${latestTag || "initial"}, skipping`,
);
} else {
output("version", result.version);
output("tag", result.tag);
console.log(
log(
`${
result.tag === "canary" ? "Canary" : "Release"
} version: ${result.version}`,
Expand Down
35 changes: 25 additions & 10 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,27 +42,42 @@ jobs:
with:
node-version: 24

- name: Check if version exists
- name: Check if version exists on npm
if: steps.version.outputs.skip != 'true'
id: check
id: npm
run: |
if npm view "opencode-graphiti@${{ steps.version.outputs.version }}" version 2>/dev/null; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "Version ${{ steps.version.outputs.version }} already exists, skipping"
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "Version ${{ steps.version.outputs.version }} already exists on npm, skipping publish"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "publish=true" >> "$GITHUB_OUTPUT"
fi

- name: Publish
if: steps.version.outputs.skip != 'true' && steps.check.outputs.skip != 'true'
if: steps.version.outputs.skip != 'true' && steps.npm.outputs.publish == 'true'
Comment thread
vicary marked this conversation as resolved.
working-directory: dist
run: npm publish --provenance --access public --tag ${{ steps.version.outputs.tag }}

- name: Tag and Release
if: github.event_name == 'push' && steps.version.outputs.skip != 'true' && steps.check.outputs.skip != 'true'
if: github.event_name == 'push' && steps.version.outputs.skip != 'true'
Comment thread
vicary marked this conversation as resolved.
run: |
Comment thread
vicary marked this conversation as resolved.
git tag "v${{ steps.version.outputs.version }}"
git push origin "v${{ steps.version.outputs.version }}"
gh release create "v${{ steps.version.outputs.version }}" --generate-notes
set -euo pipefail
tag="v${{ steps.version.outputs.version }}"

if git show-ref --verify --quiet "refs/tags/$tag"; then
echo "Tag $tag already exists locally"
elif git ls-remote --exit-code --tags origin "refs/tags/$tag" >/dev/null 2>&1; then
git fetch --tags origin
echo "Tag $tag already exists on origin"
else
git tag "$tag"
git push origin "$tag"
fi

if gh release view "$tag" >/dev/null 2>&1; then
echo "Release $tag already exists"
else
gh release create "$tag" --generate-notes
fi
env:
GH_TOKEN: ${{ github.token }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.opencode/
.swarm/

dist/

node_modules/
.worktrees/
Loading
Loading