Skip to content
Closed
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
8 changes: 4 additions & 4 deletions .claude/skills/contributing-to-gittensory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ approved, then confirm it's green.
**Install the gittensory MCP** — your pre-submit oracle that predicts the gate before you push:

```sh
npm install -g @jsonbored/gittensory-mcp@latest
npm install -g @jsonbored/loopover-mcp@latest
gittensory-mcp login # GitHub device flow (for the auth'd preflight tools)
gittensory-mcp init-client --print codex # prints TOML for ~/.codex/config.toml ([mcp_servers.gittensory])
gittensory-mcp init-client --print claude # or --print cursor — prints the correct config per tool
Expand Down Expand Up @@ -204,7 +204,7 @@ Run the matching command(s) and **commit the regenerated file(s)** — CI fails
| Added a raw-SQL migration | (none — just author it) | next **contiguous** `migrations/NNNN_snake.sql` |
| `src/selfhost/**` (or a few other scanned files — see `scripts/gen-selfhost-env-reference.mjs`'s `DEFAULT_SOURCE_ROOTS`) adding/removing an `env.SOMETHING` read | `npm run selfhost:env-reference` | `apps/gittensory-ui/src/lib/selfhost-env-reference.ts` — the doc cites the file only (not `file:line`, deliberately, so an unrelated line shift elsewhere in the file never makes this go stale) |
| CLI command surface | `npm run command-reference` | the generated command-reference doc |
| UI files (`apps/gittensory-ui/**`) | `npm --workspace @jsonbored/gittensory-ui run format` | formatted files |
| UI files (`apps/gittensory-ui/**`) | `npm --workspace @jsonbored/loopover-ui run format` | formatted files |

Migrations must use the **next free number** (contiguous, no gaps, no reuse) and match
`NNNN_snake_case.sql`; `db:migrations:check` enforces it.
Expand All @@ -222,14 +222,14 @@ npm audit --audit-level=moderate # the dependency-review job's local eq
`npm run test:ci` runs, and must pass, **all of**: `actionlint`, `db:migrations:check`,
`db:schema-drift:check`, `selfhost:env-reference:check`, `selfhost:validate-observability`,
`cf-typegen:check`, `typecheck`, `test:coverage`, `test:engine-parity`, `test:driver-parity`, the
`@jsonbored/gittensory-engine` workspace's own test run, `test:workers`, `build:mcp`, `test:mcp-pack`,
`@jsonbored/loopover-engine` workspace's own test run, `test:workers`, `build:mcp`, `test:mcp-pack`,
`build:miner`, `test:miner-pack`, `rees:test`, `ui:openapi:check`, `ui:openapi:settings-parity`,
`ui:version-audit`, `docs:drift-check`, `manifest:drift-check`, `engine-parity:drift-check`,
`command-reference:check`, `ui:lint`, `ui:typecheck`, `ui:test`, `ui:build`. If any step fails, fix it
and re-run — do not push a red tree. (Full per-check table in `reference.md`; check `package.json`'s
own `test:ci` script if this list and that script ever disagree — the script is the source of truth.)

If `ui:lint` fails on formatting, run `npm --workspace @jsonbored/gittensory-ui run format`. If
If `ui:lint` fails on formatting, run `npm --workspace @jsonbored/loopover-ui run format`. If
`ui:openapi:check` fails, you forgot Phase 4's `ui:openapi`.

**Sync with `main` before you push if it moved** — a base conflict auto-closes a contributor PR:
Expand Down
10 changes: 5 additions & 5 deletions .claude/skills/contributing-to-gittensory/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ these for a normal PR:**
| Local command | Why it's not in the table above |
|---|---|
| `npm run test:engine-parity`, `npm run test:driver-parity` | Plain `test/contract/*.test.ts` files — no dedicated CI job, but they DO run in CI as part of whichever `test (1/2)` shard happens to contain them (sharded `vitest run`). |
| `npm run test --workspace @jsonbored/gittensory-engine` | The engine package's own `node --test` suite. **Not run by `ci.yml` on a PR at all** — only by `.github/workflows/publish-engine.yml` at release time. A regression here is invisible to Codecov and to every PR-gating CI check; `test:ci` locally is the only pre-merge signal. |
| `npm run test --workspace @jsonbored/loopover-engine` | The engine package's own `node --test` suite. **Not run by `ci.yml` on a PR at all** — only by `.github/workflows/publish-engine.yml` at release time. A regression here is invisible to Codecov and to every PR-gating CI check; `test:ci` locally is the only pre-merge signal. |
| `npm run manifest:drift-check`, `npm run engine-parity:drift-check` | Appear in `test:ci` only — not in `ci.yml` under any job. |

This is a real, previously-hit gap, not a hypothetical: a past PR shipped a genuine, undetected
`codecov/patch`-adjacent regression in the engine package specifically because `test --workspace
@jsonbored/gittensory-engine` isn't part of `ci.yml`. If your change touches
@jsonbored/loopover-engine` isn't part of `ci.yml`. If your change touches
`packages/gittensory-engine/**`, running `npm run test:ci` locally (not just watching the PR's CI
checks go green) is the only way to know you didn't break it.

Expand Down Expand Up @@ -138,12 +138,12 @@ PR-side signals that raise slop risk (band: clean 0 / low 1–24 / elevated 25

---

## 5. MCP pre-submit tools (`@jsonbored/gittensory-mcp`)
## 5. MCP pre-submit tools (`@jsonbored/loopover-mcp`)

Install + configure (let the CLI print the right config for your tool — **Codex is TOML, not JSON**):

```sh
npm install -g @jsonbored/gittensory-mcp@latest
npm install -g @jsonbored/loopover-mcp@latest
gittensory-mcp login # GitHub device flow
gittensory-mcp init-client --print codex # → ~/.codex/config.toml ([mcp_servers.gittensory])
gittensory-mcp init-client --print claude # or --print cursor (→ mcpServers JSON)
Expand Down Expand Up @@ -240,7 +240,7 @@ absence of forbidden terms.
## 7. Style, lint, naming

- **Prettier (UI):** printWidth 100, double quotes, semicolons, `trailingComma: all`. Fix:
`npm --workspace @jsonbored/gittensory-ui run format`. (`routeTree.gen.ts` and lockfiles are ignored.)
`npm --workspace @jsonbored/loopover-ui run format`. (`routeTree.gen.ts` and lockfiles are ignored.)
- **ESLint (UI):** `react-hooks/recommended`; `react-refresh/only-export-components` (non-component
exports must move to a `*-model.ts`); **design-token** `no-restricted-syntax` in
`src/components/site/**` + `src/routes/**` (use `text-token-*`, `leading-token-*`, `rounded-token`,
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ jobs:
# subprocess via StdioClientTransport -- none of the 6 ever load root src/ in-process. This mirrors
# the `mcp` filter's own established trust boundary above (a self-contained package build). They are
# NOT self-contained w.r.t. packages/gittensory-engine/**, though: gittensory-mcp now has a real
# dependency on @jsonbored/gittensory-engine (isTestFile/isCodeFile), which the bin subprocess and
# dependency on @jsonbored/loopover-engine (isTestFile/isCodeFile), which the bin subprocess and
# local-branch.js load at runtime -- hence that path below, alongside the mcp package's own.
# test/unit/mcp-output-schemas.test.ts is DELIBERATELY NOT in this filter: unlike the other 6, it
# imports src/mcp/server.ts in-process, and server.ts alone directly imports ~40 other src/ modules
Expand Down Expand Up @@ -274,7 +274,7 @@ jobs:
run: node scripts/validate-observability-configs.mjs
# Runs ahead of Typecheck AND "Test with coverage" (#ci-engine-build-order): src/mcp/find-opportunities.ts
# (root backend, since #2281/#3985) imports packages/gittensory-miner/lib/opportunity-fanout.js --
# committed, pre-built JS -- which itself imports @jsonbored/gittensory-engine. That package's dist/ is
# committed, pre-built JS -- which itself imports @jsonbored/loopover-engine. That package's dist/ is
# gitignored and only exists after this build step, so ANY backend typecheck or test run needs it built
# first, not just a PR that touches packages/gittensory-engine/** directly (this step's original, too-
# narrow trigger). Originally ran after coverage (broke test:coverage on PRs that didn't touch the
Expand All @@ -283,7 +283,7 @@ jobs:
# of both.
- name: Build engine package
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' || needs.changes.outputs.engine == 'true' || needs.changes.outputs.ui == 'true' }}
run: npm run build --workspace @jsonbored/gittensory-engine
run: npm run build --workspace @jsonbored/loopover-engine
# .tsbuildinfo mutates every run (tsc's own incremental state), unlike node_modules above which is
# immutable per lockfile -- so this needs the run_id-suffixed-key + restore-keys-prefix pattern (always
# creates a new cache entry to save into, restore falls back to the most recent matching prefix) rather
Expand All @@ -310,7 +310,7 @@ jobs:
- name: Worker runtime tests
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' }}
run: npm run test:workers
# gittensory-mcp now depends on @jsonbored/gittensory-engine for real (isTestFile/isCodeFile), so a
# gittensory-mcp now depends on @jsonbored/loopover-engine for real (isTestFile/isCodeFile), so a
# PR that only touches packages/gittensory-engine/** must also rebuild + pack-check the mcp package,
# not just the mcp filter's own (deliberately narrower) path list.
- name: Build MCP
Expand Down Expand Up @@ -389,7 +389,7 @@ jobs:
# steps directly instead of the aggregate script, skipping that redundant regen.
- name: UI build
if: ${{ github.event_name == 'push' || needs.changes.outputs.ui == 'true' }}
run: npm run extension:build && npm --workspace @jsonbored/gittensory-ui run build
run: npm run extension:build && npm --workspace @jsonbored/loopover-ui run build

# The full-suite coverage run, sharded (#ci-shard-coverage). This alone was ~9-10 of the ~11 minutes a
# typical backend PR spent in validate-code, because vitest schedules whole test FILES atomically to
Expand Down Expand Up @@ -463,7 +463,7 @@ jobs:
# Any backend test run needs the engine package's dist/ built first -- see the identical step's
# comment in validate-code (#ci-engine-build-order) for why.
- name: Build engine package
run: npm run build --workspace @jsonbored/gittensory-engine
run: npm run build --workspace @jsonbored/loopover-engine
- name: Prepare test reports dir
run: mkdir -p reports/junit
- name: Test with coverage (shard ${{ matrix.shard }}/6)
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ jobs:
run: npm ci

# packages/gittensory-mcp's own test suite pulls in src/mcp/find-opportunities.ts transitively
# (via packages/gittensory-miner/lib/opportunity-fanout.js), which imports @jsonbored/gittensory-engine.
# That package's dist/ is gitignored (see ci.yml's own build --workspace @jsonbored/gittensory-engine
# (via packages/gittensory-miner/lib/opportunity-fanout.js), which imports @jsonbored/loopover-engine.
# That package's dist/ is gitignored (see ci.yml's own build --workspace @jsonbored/loopover-engine
# step for the same reason) -- without building it first, the MCP release validation gate below
# fails with "Failed to resolve entry for package @jsonbored/gittensory-engine" on every run.
# fails with "Failed to resolve entry for package @jsonbored/loopover-engine" on every run.
- name: Build gittensory-engine
run: npm run build --workspace @jsonbored/gittensory-engine
run: npm run build --workspace @jsonbored/loopover-engine

- name: MCP release validation gate
run: npm run test:release:mcp
Expand All @@ -93,7 +93,7 @@ jobs:
- name: Pack and smoke-test the tarball
run: |
set -euo pipefail
PACK_JSON="$(npm pack --workspace @jsonbored/gittensory-mcp --pack-destination "$RUNNER_TEMP" --json)"
PACK_JSON="$(npm pack --workspace @jsonbored/loopover-mcp --pack-destination "$RUNNER_TEMP" --json)"
TARBALL="$(node -e 'const fs=require("fs"); const input=fs.readFileSync(0,"utf8"); process.stdout.write(JSON.parse(input)[0].filename)' <<< "$PACK_JSON")"
TARBALL_PATH="$RUNNER_TEMP/$TARBALL"
UNEXPECTED_FILES="$(tar -tzf "$TARBALL_PATH" | grep -Ev '^(package/(bin|lib|scripts)/.+|package/(package.json|README.md|CHANGELOG.md|LICENSE))$' || true)"
Expand Down Expand Up @@ -158,7 +158,7 @@ jobs:
echo "Creating tag $TAG at HEAD ($HEAD_SHA)."
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "@jsonbored/gittensory-mcp v${VERSION}"
git tag -a "$TAG" -m "@jsonbored/loopover-mcp v${VERSION}"
git remote set-url origin "https://github.com/${GITHUB_REPOSITORY}.git"
gh auth setup-git
git push origin "$TAG"
Expand Down Expand Up @@ -204,16 +204,16 @@ jobs:
set -euo pipefail
NOTES_FILE="$(mktemp)"
cat > "$NOTES_FILE" <<EOF
Published [@jsonbored/gittensory-mcp v${RELEASE_VERSION}](https://www.npmjs.com/package/@jsonbored/gittensory-mcp/v/${RELEASE_VERSION}) to npm with provenance.
Published [@jsonbored/loopover-mcp v${RELEASE_VERSION}](https://www.npmjs.com/package/@jsonbored/loopover-mcp/v/${RELEASE_VERSION}) to npm with provenance.

Install:

\`\`\`sh
npm install -g @jsonbored/gittensory-mcp@${RELEASE_VERSION}
npm install -g @jsonbored/loopover-mcp@${RELEASE_VERSION}
\`\`\`
EOF
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/gittensory-mcp v${RELEASE_VERSION}" --notes-file "$NOTES_FILE"
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/loopover-mcp v${RELEASE_VERSION}" --notes-file "$NOTES_FILE"
else
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/gittensory-mcp v${RELEASE_VERSION}" --notes-file "$NOTES_FILE" --verify-tag
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/loopover-mcp v${RELEASE_VERSION}" --notes-file "$NOTES_FILE" --verify-tag
fi
20 changes: 10 additions & 10 deletions .github/workflows/publish-engine.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Publish Engine Package
# released_by_release_please left false) is the human override path and self-tags HEAD from
# packages/gittensory-engine/package.json's version.
#
# NOTE: this workflow cannot run successfully until @jsonbored/gittensory-engine has been bootstrap
# NOTE: this workflow cannot run successfully until @jsonbored/loopover-engine has been bootstrap
# -published to npm once from a maintainer's own authenticated `npm login` session (npm's trusted
# publishing/OIDC cannot create a brand-new package -- the package must already exist before a
# Trusted Publisher can be configured in npmjs.com's package settings). Until that one-time manual
Expand Down Expand Up @@ -90,14 +90,14 @@ jobs:
run: npm ci

- name: Engine test suite
run: npm run test --workspace @jsonbored/gittensory-engine
run: npm run test --workspace @jsonbored/loopover-engine

# Build + pack happen in THIS unprivileged job (no id-token). The privileged publish job below
# never runs npm install/build, so a compromised build dependency can't reach the OIDC token.
- name: Pack and smoke-test the tarball
run: |
set -euo pipefail
PACK_JSON="$(npm pack --workspace @jsonbored/gittensory-engine --pack-destination "$RUNNER_TEMP" --json)"
PACK_JSON="$(npm pack --workspace @jsonbored/loopover-engine --pack-destination "$RUNNER_TEMP" --json)"
TARBALL="$(node -e 'const fs=require("fs"); const input=fs.readFileSync(0,"utf8"); process.stdout.write(JSON.parse(input)[0].filename)' <<< "$PACK_JSON")"
TARBALL_PATH="$RUNNER_TEMP/$TARBALL"
UNEXPECTED_FILES="$(tar -tzf "$TARBALL_PATH" | grep -Ev '^(package/dist/.+|package/(package.json|README.md|CHANGELOG.md|LICENSE))$' || true)"
Expand All @@ -114,8 +114,8 @@ jobs:
npm --prefix "$TMP" init -y >/dev/null
npm --prefix "$TMP" install "$TARBALL_PATH" >/dev/null
node --input-type=module -e "
import { ENGINE_VERSION } from '$TMP/node_modules/@jsonbored/gittensory-engine/dist/index.js';
import { isTestPath, isCodeFile } from '$TMP/node_modules/@jsonbored/gittensory-engine/dist/signals/test-evidence.js';
import { ENGINE_VERSION } from '$TMP/node_modules/@jsonbored/loopover-engine/dist/index.js';
import { isTestPath, isCodeFile } from '$TMP/node_modules/@jsonbored/loopover-engine/dist/signals/test-evidence.js';
if (typeof ENGINE_VERSION !== 'string' || !ENGINE_VERSION) throw new Error('ENGINE_VERSION smoke test failed');
if (isTestPath('test/foo.test.ts') !== true) throw new Error('isTestPath smoke test failed');
if (isCodeFile('src/foo.ts') !== true) throw new Error('isCodeFile smoke test failed');
Expand Down Expand Up @@ -178,7 +178,7 @@ jobs:
echo "Creating tag $TAG at HEAD ($HEAD_SHA)."
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "@jsonbored/gittensory-engine v${VERSION}"
git tag -a "$TAG" -m "@jsonbored/loopover-engine v${VERSION}"
git remote set-url origin "https://github.com/${GITHUB_REPOSITORY}.git"
gh auth setup-git
git push origin "$TAG"
Expand Down Expand Up @@ -224,16 +224,16 @@ jobs:
set -euo pipefail
NOTES_FILE="$(mktemp)"
cat > "$NOTES_FILE" <<EOF
Published [@jsonbored/gittensory-engine v${RELEASE_VERSION}](https://www.npmjs.com/package/@jsonbored/gittensory-engine/v/${RELEASE_VERSION}) to npm with provenance.
Published [@jsonbored/loopover-engine v${RELEASE_VERSION}](https://www.npmjs.com/package/@jsonbored/loopover-engine/v/${RELEASE_VERSION}) to npm with provenance.

Install:

\`\`\`sh
npm install @jsonbored/gittensory-engine@${RELEASE_VERSION}
npm install @jsonbored/loopover-engine@${RELEASE_VERSION}
\`\`\`
EOF
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/gittensory-engine v${RELEASE_VERSION}" --notes-file "$NOTES_FILE"
gh release edit "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/loopover-engine v${RELEASE_VERSION}" --notes-file "$NOTES_FILE"
else
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/gittensory-engine v${RELEASE_VERSION}" --notes-file "$NOTES_FILE" --verify-tag
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --title "@jsonbored/loopover-engine v${RELEASE_VERSION}" --notes-file "$NOTES_FILE" --verify-tag
fi
4 changes: 2 additions & 2 deletions .github/workflows/release-selfhost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ jobs:
run: npm ci --ignore-scripts

# #ci-engine-build-order (release pipeline): --all bundles EVERYTHING self-contained, including
# packages/gittensory-miner/lib/*.js (committed, pre-built) which imports @jsonbored/gittensory-engine.
# packages/gittensory-miner/lib/*.js (committed, pre-built) which imports @jsonbored/loopover-engine.
# That package's dist/ is gitignored and only exists after this build step -- the regular CI smoke
# test's narrower (non --all) build never hits this import chain, so it never caught the gap that
# ci.yml's own validate-code job hit for ordinary backend PRs (fixed there separately).
- name: Build engine package
run: npm run build --workspace @jsonbored/gittensory-engine
run: npm run build --workspace @jsonbored/loopover-engine

- name: Build self-host bundle for release
run: node scripts/build-selfhost.mjs --all
Expand Down
Loading
Loading