diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 6494fac53..d349515db 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -6,7 +6,7 @@ "plugins": [ { "name": "gitnexus", - "version": "1.6.9", + "version": "1.6.9-aptos", "source": { "source": "local", "path": "./gitnexus-claude-plugin" diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 576586d48..b022f9b42 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ "plugins": [ { "name": "gitnexus", - "version": "1.6.9", + "version": "1.6.9-aptos", "source": "./gitnexus-claude-plugin", "description": "Code intelligence powered by a knowledge graph. Provides execution flow tracing, blast radius analysis, and augmented search across your codebase." } diff --git a/.github/APTOS_RELEASES.md b/.github/APTOS_RELEASES.md new file mode 100644 index 000000000..ce8aaffeb --- /dev/null +++ b/.github/APTOS_RELEASES.md @@ -0,0 +1,38 @@ +# Aptos release line + +The `main-aptos` branch carries the compiler-backed Move integration separately +from the compilerless `main` release line. Keep it current by regularly merging +`main` into `main-aptos` through a pull request. Do not rewrite the shared branch: +published Aptos tags must remain reachable from it. + +## Release an Aptos version + +1. Synchronize `main-aptos` with `main` and pass the full CI suite. +2. Choose an unused npm prerelease version: `X.Y.Z-aptos` for the first release + on a base version, then `X.Y.Z-aptos.N` for later revisions. +3. From `gitnexus/`, run: + + ```bash + npm version X.Y.Z-aptos --no-git-tag-version + ``` + + This synchronizes `package.json`, `package-lock.json`, and the plugin + manifests. Commit the version change to `main-aptos`. +4. Tag that versioned commit and push the tag: + + ```bash + git tag -a vX.Y.Z-aptos -m vX.Y.Z-aptos + git push origin main-aptos + git push origin vX.Y.Z-aptos + ``` + +The publish workflow rejects tags that do not match the package version or are +not contained in `main-aptos`. A valid tag publishes the exact npm version under +the `aptos` dist-tag, so users can install either form: + +```bash +npm install gitnexus@X.Y.Z-aptos +npm install gitnexus@aptos +``` + +Aptos releases are GitHub prereleases and never update npm's `latest` tag. diff --git a/.github/workflows/build-tree-sitter-prebuilds.yml b/.github/workflows/build-tree-sitter-prebuilds.yml index 7a7acb73a..92f3ef0fc 100644 --- a/.github/workflows/build-tree-sitter-prebuilds.yml +++ b/.github/workflows/build-tree-sitter-prebuilds.yml @@ -75,7 +75,7 @@ on: type: boolean default: true pull_request: - branches: [main] + branches: [main, main-aptos] paths: # Any build-affecting change under a vendored grammar triggers a rebuild — # not just a version bump — so editing the vendored source (parser.c, diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ea361fb6..a93cc16f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: pull_request: - branches: [main] + branches: [main, main-aptos] paths-ignore: ['**.md', 'docs/**', 'LICENSE'] workflow_call: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a72adeed3..efd6990f6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -8,7 +8,7 @@ name: CodeQL on: pull_request: - branches: [main] + branches: [main, main-aptos] paths-ignore: ['**.md', 'docs/**', 'LICENSE'] push: branches: [main] diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 58740cd09..26258ddbd 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -8,7 +8,7 @@ name: Dependency Review on: pull_request: - branches: [main] + branches: [main, main-aptos] permissions: contents: read diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml index 3f0c50f4e..3087be412 100644 --- a/.github/workflows/gitleaks.yml +++ b/.github/workflows/gitleaks.yml @@ -8,7 +8,7 @@ name: Gitleaks on: pull_request: - branches: [main] + branches: [main, main-aptos] push: branches: [main] diff --git a/.github/workflows/pr-description-check.yml b/.github/workflows/pr-description-check.yml index cec32de0f..23e562316 100644 --- a/.github/workflows/pr-description-check.yml +++ b/.github/workflows/pr-description-check.yml @@ -3,7 +3,7 @@ name: PR Description Check on: pull_request: types: [opened, edited, reopened] - branches: [main] + branches: [main, main-aptos] permissions: pull-requests: write diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1623698a7..c5f3b6cf6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,7 +5,7 @@ name: Publish # images. Replaces the former two-workflow design — see issue #1609 for the # double-publish race this unification closes. # -# Two release modes, both routed through this file: +# Three release modes, all routed through this file: # • Release candidate (rc) — triggered by push to `main` or workflow_dispatch. # The RC path computes the next rc version, applies it in-CI, pushes a # detached release commit with v-rc. + rc/ marker @@ -14,6 +14,10 @@ name: Publish # • Stable — triggered by push of a v tag (no -rc.* # suffix). Verifies package.json matches the tag, publishes to npm with # --tag latest, creates a stable GitHub Release. No docker (RC-only). +# • Aptos — triggered by push of a v-aptos[.] +# tag. Verifies package.json matches the tag and that the tagged commit is +# contained in main-aptos, publishes to npm with --tag aptos, and creates +# a GitHub prerelease. No docker (RC-only). # # ⚠️ SELF-TRIGGER INVARIANT — DO NOT WEAKEN ⚠️ # The `tags:` filter below uses a negative glob `'!v*-rc.*'` to prevent the @@ -123,13 +127,16 @@ jobs: ;; refs/tags/v*) # The trigger filter already excluded v*-rc.* tags. Anything - # reaching here is either a stable semver or a malformed v*. + # reaching here is a stable semver, an Aptos release, or a + # malformed v* tag. TAG="${GH_REF#refs/tags/}" if [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then MODE="stable" + elif [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-aptos(\.[0-9]+)?$ ]]; then + MODE="aptos" else echo "::error::malformed v* tag rejected: ${REF_NAME_SAFE}" - echo "::error::stable tags must match ^v[0-9]+\\.[0-9]+\\.[0-9]+\$" + echo "::error::accepted tags are vX.Y.Z or vX.Y.Z-aptos[.N]" exit 1 fi ;; @@ -255,13 +262,13 @@ jobs: fi # ── Phase 3: reusable CI gate ────────────────────────────────────────────── - # Runs for both rc (when guard says go) and stable. No `secrets:` passed — + # Runs for rc (when guard says go), stable, and Aptos. No `secrets:` passed — # ci.yml and its entire reusable-workflow chain (ci-quality, ci-tests, # ci-e2e, ci-report) reference zero `secrets.*` values; # passing any would be unused surface. GITHUB_TOKEN is implicit. ci: needs: [route, rc-guard] - if: ${{ always() && (needs.route.outputs.mode == 'stable' || needs.rc-guard.outputs.should_run == 'true') }} + if: ${{ always() && (needs.route.outputs.mode == 'stable' || needs.route.outputs.mode == 'aptos' || needs.rc-guard.outputs.should_run == 'true') }} uses: ./.github/workflows/ci.yml permissions: contents: read @@ -277,7 +284,7 @@ jobs: publish: name: Publish to npm needs: [route, rc-guard, ci] - if: ${{ always() && needs.ci.result == 'success' && (needs.route.outputs.mode == 'stable' || needs.rc-guard.outputs.should_run == 'true') }} + if: ${{ always() && needs.ci.result == 'success' && (needs.route.outputs.mode == 'stable' || needs.route.outputs.mode == 'aptos' || needs.rc-guard.outputs.should_run == 'true') }} runs-on: ubuntu-latest timeout-minutes: 20 permissions: @@ -322,6 +329,9 @@ jobs: # which the action resolves correctly. client-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + permission-contents: write + permission-workflows: write + permission-metadata: read # ── Separate checkout steps per mode ───────────────────────────────── # Conditional `token:` expressions are footguns: empty string passed to @@ -347,13 +357,14 @@ jobs: # `Create and push rc tags` step below. persist-credentials: false - - name: Checkout (stable) - if: needs.route.outputs.mode == 'stable' + - name: Checkout (tagged release) + if: needs.route.outputs.mode == 'stable' || needs.route.outputs.mode == 'aptos' uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - # No `token:` — actions/checkout uses GITHUB_TOKEN by default. Stable - # path performs no git pushes; the default scope is sufficient. + # No `token:` — actions/checkout uses GITHUB_TOKEN by default. Tagged + # release paths perform no git pushes; the default scope is sufficient. with: - # No git pushes from the stable path either. Skip credential + fetch-depth: 0 + # No git pushes from tagged release paths. Skip credential # persistence (artipacked audit). persist-credentials: false @@ -404,28 +415,48 @@ jobs: run: npm ci working-directory: gitnexus - # ── Stable-only: verify the tag and package.json agree ─────────────── - - name: Verify version consistency (stable) - if: needs.route.outputs.mode == 'stable' + # ── Tagged releases: verify tag, source branch, and package version ── + - name: Verify version consistency (tagged release) + if: needs.route.outputs.mode == 'stable' || needs.route.outputs.mode == 'aptos' shell: bash working-directory: gitnexus + env: + RELEASE_MODE: ${{ needs.route.outputs.mode }} run: | set -euo pipefail TAG_VERSION="${GITHUB_REF#refs/tags/v}" - # Stable mode REJECTS prerelease suffixes — those are filtered at - # trigger by the negative-glob filter, but defend at the bash layer too. - if ! [[ "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Stable tag must be ^v[0-9]+.[0-9]+.[0-9]+$ — got v$TAG_VERSION" - exit 1 - fi + case "$RELEASE_MODE" in + stable) + if ! [[ "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Stable tag must be vX.Y.Z — got v$TAG_VERSION" + exit 1 + fi + ;; + aptos) + if ! [[ "$TAG_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-aptos(\.[0-9]+)?$ ]]; then + echo "::error::Aptos tag must be vX.Y.Z-aptos[.N] — got v$TAG_VERSION" + exit 1 + fi + # Aptos releases must come from the maintained main-aptos line, + # not from an arbitrary commit carrying a syntactically valid tag. + git fetch --no-tags origin main-aptos:refs/remotes/origin/main-aptos + if ! git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main-aptos; then + echo "::error::Aptos tag v$TAG_VERSION is not contained in origin/main-aptos." + exit 1 + fi + ;; + *) + echo "::error::Unexpected tagged release mode: $RELEASE_MODE" + exit 1 + ;; + esac PKG_VERSION=$(node -p "require('./package.json').version") if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then echo "::error::Tag version (v$TAG_VERSION) does not match package.json version ($PKG_VERSION)" exit 1 fi - # Stable releases carry their version bump on main via the release - # PR, so the manifest surfaces must already be in sync — refuse to - # publish a stable whose manifests drifted (#2445). + # Tagged releases carry their version in the tagged tree, so all + # manifest surfaces must already be in sync (#2445). node scripts/sync-plugin-manifests.mjs --check echo "Version verified: $PKG_VERSION" @@ -608,7 +639,7 @@ jobs: shell: bash working-directory: gitnexus env: - NPM_TAG: ${{ needs.route.outputs.mode == 'rc' && 'rc' || 'latest' }} + NPM_TAG: ${{ needs.route.outputs.mode == 'rc' && 'rc' || needs.route.outputs.mode == 'aptos' && 'aptos' || 'latest' }} run: npm publish --dry-run --tag "$NPM_TAG" # ── Acquire the "rc lock" BEFORE publishing (idempotency anchor) ───── @@ -725,9 +756,9 @@ jobs: echo "release_sha=$RELEASE_SHA" } >> "$GITHUB_OUTPUT" - - name: Set vtag (stable) + - name: Set vtag (tagged release) id: stable-vtag - if: needs.route.outputs.mode == 'stable' + if: needs.route.outputs.mode == 'stable' || needs.route.outputs.mode == 'aptos' shell: bash # github.ref_name flows in via env to avoid templating into the # shell source (template-injection audit). Even though refs are @@ -770,6 +801,12 @@ jobs: exit 1 fi ;; + aptos) + if ! [[ "$VTAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-aptos(\.[0-9]+)?$ ]]; then + echo "::error::vtag '${VTAG}' does not match Aptos shape ^vX.Y.Z-aptos[.N]$" + exit 1 + fi + ;; *) echo "::error::unknown mode '${MODE}' at vtag integrity gate." exit 1 @@ -778,6 +815,7 @@ jobs: echo "vtag verified: ${VTAG} (mode=${MODE})" echo "vtag=${VTAG}" >> "$GITHUB_OUTPUT" + echo "version=${VTAG#v}" >> "$GITHUB_OUTPUT" # npm Trusted Publishing (GA'd 2025-07-31). OIDC authentication only # engages when no npm credential is configured anywhere — the absence @@ -808,7 +846,7 @@ jobs: shell: bash working-directory: gitnexus env: - NPM_TAG: ${{ needs.route.outputs.mode == 'rc' && 'rc' || 'latest' }} + NPM_TAG: ${{ needs.route.outputs.mode == 'rc' && 'rc' || needs.route.outputs.mode == 'aptos' && 'aptos' || 'latest' }} run: npm publish --access public --tag "$NPM_TAG" # ── Stable-only: pull CHANGELOG body if present ────────────────────── @@ -834,8 +872,10 @@ jobs: name: >- ${{ needs.route.outputs.mode == 'rc' && format('Release Candidate {0}', steps.vtag-gate.outputs.vtag) + || needs.route.outputs.mode == 'aptos' + && format('Aptos {0}', steps.vtag-gate.outputs.vtag) || steps.vtag-gate.outputs.vtag }} - prerelease: ${{ needs.route.outputs.mode == 'rc' }} + prerelease: ${{ needs.route.outputs.mode == 'rc' || needs.route.outputs.mode == 'aptos' }} make_latest: ${{ needs.route.outputs.mode == 'stable' && 'true' || 'false' }} # Stable: prefer CHANGELOG body, fall back to auto-generated. # RC: always auto-generated + the prerelease body block below. @@ -844,9 +884,14 @@ jobs: && '/tmp/release-notes.md' || '' }} generate_release_notes: >- ${{ needs.route.outputs.mode == 'rc' + || needs.route.outputs.mode == 'aptos' || steps.changelog.outputs.fallback == 'true' }} body: >- - ${{ needs.route.outputs.mode == 'rc' && format( + ${{ needs.route.outputs.mode == 'aptos' && format( + 'Aptos-specific GitNexus distribution from `main-aptos`.{0}{0}**npm:** `npm install gitnexus@{1}`{0}**npm channel:** `npm install gitnexus@aptos`{0}{0}This release includes the compiler-backed Move integration and is maintained separately from the compilerless core release.', + '\n', + steps.vtag-gate.outputs.version + ) || needs.route.outputs.mode == 'rc' && format( 'Automated release candidate build from `main`.{0}{0}**npm:** `npm install gitnexus@rc`{0}**Version:** `{1}`{0}**Target base:** `{2}` (rc #{3}){0}**Source commit (main):** {4}{0}**Release commit (versioned tree):** {5}{0}{0}Release candidates are pre-stable builds intended for early testing. Stable releases remain on the `latest` dist-tag.', '\n', steps.rc-version.outputs.rc_version, diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index ba61226f7..34885fc2b 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -11,7 +11,7 @@ name: Workflow Lint on: pull_request: - branches: [main] + branches: [main, main-aptos] paths: - '.github/**' diff --git a/gitnexus-claude-plugin/.claude-plugin/plugin.json b/gitnexus-claude-plugin/.claude-plugin/plugin.json index ace058dad..9d37f7daa 100644 --- a/gitnexus-claude-plugin/.claude-plugin/plugin.json +++ b/gitnexus-claude-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gitnexus", "description": "Code intelligence powered by a knowledge graph. Provides execution flow tracing, blast radius analysis, and augmented search across your codebase.", - "version": "1.6.9", + "version": "1.6.9-aptos", "author": { "name": "GitNexus" }, diff --git a/gitnexus-claude-plugin/.codex-plugin/plugin.json b/gitnexus-claude-plugin/.codex-plugin/plugin.json index c9a03db4d..e66906ee2 100644 --- a/gitnexus-claude-plugin/.codex-plugin/plugin.json +++ b/gitnexus-claude-plugin/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gitnexus", "description": "Code intelligence powered by a knowledge graph. Provides execution flow tracing, blast radius analysis, and augmented search across your codebase.", - "version": "1.6.9", + "version": "1.6.9-aptos", "skills": "./skills", "mcpServers": "./.mcp.json", "hooks": "./hooks/hooks.json", diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index 9aa2ef8da..776575c6e 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -1,12 +1,12 @@ { "name": "gitnexus", - "version": "1.6.9", + "version": "1.6.9-aptos", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitnexus", - "version": "1.6.9", + "version": "1.6.9-aptos", "hasInstallScript": true, "license": "PolyForm-Noncommercial-1.0.0", "dependencies": { diff --git a/gitnexus/package.json b/gitnexus/package.json index 48a142cdf..d2870e43f 100644 --- a/gitnexus/package.json +++ b/gitnexus/package.json @@ -1,6 +1,6 @@ { "name": "gitnexus", - "version": "1.6.9", + "version": "1.6.9-aptos", "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.", "author": "Abhigyan Patwari", "license": "PolyForm-Noncommercial-1.0.0", @@ -35,7 +35,12 @@ "hooks", "scripts", "skills", - "vendor", + "vendor/leiden", + "vendor/tree-sitter-c", + "vendor/tree-sitter-dart", + "vendor/tree-sitter-kotlin", + "vendor/tree-sitter-proto", + "vendor/tree-sitter-swift", "web" ], "scripts": { diff --git a/gitnexus/test/unit/cli-commands.test.ts b/gitnexus/test/unit/cli-commands.test.ts index 545a38b7f..b4799f361 100644 --- a/gitnexus/test/unit/cli-commands.test.ts +++ b/gitnexus/test/unit/cli-commands.test.ts @@ -107,7 +107,20 @@ describe('CLI commands', () => { // (vendored-grammars.ts), so postinstall no longer materializes anything. expect(pkg.default.scripts.postinstall).not.toContain('materialize-vendor-grammars.cjs'); expect(pkg.default.scripts.postinstall).toContain('build-tree-sitter-grammars.cjs'); - expect(pkg.default.files).toContain('vendor'); + expect(pkg.default.files).toEqual( + expect.arrayContaining([ + 'vendor/leiden', + 'vendor/tree-sitter-c', + 'vendor/tree-sitter-dart', + 'vendor/tree-sitter-kotlin', + 'vendor/tree-sitter-proto', + 'vendor/tree-sitter-swift', + ]), + ); + // move-flow is downloaded per-platform and must never leak from a local + // install/cache into the cross-platform npm tarball. + expect(pkg.default.files).not.toContain('vendor'); + expect(pkg.default.files).not.toContain('vendor/move-flow'); }); it('declares node-gyp-build/node-addon-api as regular dependencies (runtime-load contract)', async () => { diff --git a/gitnexus/test/unit/move/mcp-client.test.ts b/gitnexus/test/unit/move/mcp-client.test.ts index 324e07e54..3e5d59362 100644 --- a/gitnexus/test/unit/move/mcp-client.test.ts +++ b/gitnexus/test/unit/move/mcp-client.test.ts @@ -8,6 +8,13 @@ vi.mock('node:child_process', () => ({ execFileSync: vi.fn(), })); +// Keep PATH-resolution tests independent of a developer or CI cache that may +// already contain vendor/move-flow//move-flow. +vi.mock('node:fs', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, existsSync: vi.fn(() => false) }; +}); + import { MoveFlowMcpClient, MoveFlowToolCallError,