Documentation Contributing / Development

Site

Docs Contributing

Developing OpenGantry (dogfood the full stack)

This repository is the GXT specimen. Contributors and agents MUST follow the same substrate, hooks, and gantry commands we ship to adopters — not a lighter internal process.

One-time setup

npm ci
npm run build
git config core.hooksPath .githooks
gantry planner set "$(git config user.email)"

Confirm readiness:

gantry doctor

Roadmap / open work: GitHub Issues · Project board #2 · Historical backlog: docs/archive/BACKLOG.md · Release history: docs/CHANGELOG.md

Docs map: docs/index.md · root README § Documentation map.

Law + routing (before you edit)

  1. .gitagent/planner/RULES.md — SOD, trace, TMVC, Rule 4.4, break-glass.
  2. .gitagent/foreman/MANIFEST.json — skills, roots, forbidden zones.

Specimen routing (this repository only)

jeger-ai/opengantry dogfoods through the gantry skill and TMVC src/cli/ only. The specimen .gitagent/foreman/MANIFEST.json intentionally diverges from templates/.gitagent/foreman/MANIFEST.json: adopters still receive ui/logic skills and example app paths after gantry init, but this CLI-only repo does not list fictional src/components/ or src/lib/ roots.

MSN-enforced paths for commits and PR guards come from fixed substrate paths plus every tmvc_roots entry in MANIFEST (see scripts/gxt-manifest-lib.mjs).

Planner allowlist: committed .gitagent/foreman/PLANNER.allowlist (team emails for CI git-proof). Personal overrides: .gitagent/foreman/PLANNER.allowlist.local (gitignored). Run gantry planner show to confirm.

Where work belongs

ChangeSkill (typical)TMVC
src/cli/** (gantry)gantrysrc/cli/
App logic (if present)logicsrc/lib/, src/utils/
UI (if present)uisrc/components/, src/styles/
.gitagent/, hooks, workflowsPlanner + missionTier-3 — legislate first

CLI registrar conventions (program-core.ts, program-workflow.ts)

Commander .action() handlers MUST extract options from the typed callback parameter — never this.opts() (arrow handlers have no Command this).

PatternWhenExample
Pass-throughCLI flag names match the command *Options interfaceinit: (options: InitOptions) => runInit(options)
Typed adapterCommander names differ or values need coercionstart (--no-write → writeMission), verify (--reason → breakGlassReason, --scan-depth string → number)
Positional + optionsVariadic intent args(intentParts, options, _cmd) — options is the second arg; join positionals before calling run*

Numeric or transformed flags MUST coerce in .option() parsers or the command adapter — never blind pass-through. See verifyOptionsFromCli in program-workflow.ts.

Mission graph (required for substantive work)

  1. Triage — gantry triage "<intent>" (escalation → Planner legislates).
  2. Legislate — gantry legislate --from-intent "<intent>" --msn MSN-NNNN --skill-key gantry (proposes a tighten-only contract, prompts [a]/[e]/[q] or take --yes; runs gap analysis; halts with one finding when operator answers are missing — use MCP gxt_interrogate or --interrogation-file). MCP: gxt_propose_contract → gxt_draft_legislation with interrogation[] + contract. Operator answers are quoted verbatim — never fabricated by agents.
  3. Planner commit — subject [MSN-NNNN] …, author email in repo Planner allowlist, mission file under .gitagent/missions/ included in the commit.
  4. Pin — gantry pin .gitagent/missions/<file>.yaml (or scripts/gxt-pin-mission.sh …). When pinned, verify / scan / attest / runtime env default to that mission and print [gantry] Using pinned mission: …. Run gantry unpin when switching MSNs.
  5. Executor scope — source scripts/gxt-runtime-env.sh (uses pin) or eval "$(gantry runtime env)" when pinned.
  6. Trace — append PASS quotes to repo-root EXECUTOR_LOG.md (see example.verify.yaml).
  7. Verify — gantry verify (pinned) or gantry verify --mission .gitagent/missions/<file>.yaml. Optional: gantry verify --receipt then gantry receipt show.

Cursor session (this repo)

OpenGantry dogfoods GXT in Cursor. Other agents: docs/INTEGRATIONS.md.

One-time Cursor enablement

  1. Hooks on — Cursor Settings → Hooks (project hooks from .cursor/hooks.json must be enabled).
  2. MCP on — Cursor Settings → MCP. .cursor/mcp.json is byte-identical for this specimen and for adopters. The command is ./scripts/mcp-launcher.sh. There is no parity exemption for that file. Do not point Cursor at a raw node binary. The launcher runs node dist/cli/index.js mcp serve when dist/cli/index.js exists (run npm run build in this repo); otherwise it runs gantry mcp serve from PATH (@jeger-ai/opengantry@3.5.0+). Reload MCP after changing mcp.json, the launcher, or the package.
  3. Repo setup (same as above):
npm ci && npm run build
git config core.hooksPath .githooks
gantry planner set "$(git config user.email)"
gantry doctor

Restart Cursor after first clone if hooks do not appear (Output → Hooks).

Per-feature closed graph

Cursor MCP (preferred):

  1. For gate_adapter: tsc|eslint missions, Agent: gxt_doctor (optional gate_adapter, adapter_baseline) before legislation.
  2. Agent: gxt_propose_contract → gxt_interrogate (answer findings) → gxt_draft_legislation with complete interrogation[] and the approved contract → human approves → gxt_execute_legislation.
  3. Planner: run returned suggested_human_action (git commit …).
  4. Agent: gxt_check_signature → gxt_pin_mission → executor edits → gxt_verify.

CLI fallback:

gantry triage "<intent>"
gantry legislate --from-intent "<intent>" --msn MSN-NNNN --skill-key gantry   # or substrate
# Planner: git commit -m "[MSN-NNNN] legislate …" including mission file

scripts/gxt-pin-mission.sh .gitagent/missions/MSN-NNNN.<slug>.yaml
# or: gantry pin .gitagent/missions/MSN-NNNN.<slug>.yaml
# New Agent chat → sessionStart injects GXT_TMVC_* + mission context automatically

source scripts/gxt-runtime-env.sh   # integrated terminal (same pinned mission)
# … Cursor Agent work in src/cli/ or legislated scope …
# Append gate evidence to EXECUTOR_LOG.md

gantry verify            # uses pin; prints [gantry] Using pinned mission: …
gantry verify --receipt  # optional attestation receipt (gantry receipt show)
npm run validate
git push   # pre-push: gantry verify --pre-push on branch-changed missions

Validate MCP flow locally: ./scripts/validate-mcp-dogfood.sh

Substrate upgrade graph (adopters + dogfood)

Tier-3 lifecycle updates use the installed gantry package only (no remote fetch):

npm install @jeger-ai/opengantry@latest   # when a newer release exists
gantry upgrade plan --json          # preview file changes (stable schema_version 1)
gantry upgrade                     # stage managed_strict assets + draft MSN-900x mission YAML
# Review .gitagent/.upgrade-tmp/ diff; commit mission YAML only (tmp is gitignored)
git add .gitagent/missions/MSN-9001.upgrade-vX.Y.Z.yaml
git commit -m "[MSN-9001] approve substrate upgrade to vX.Y.Z"
gantry upgrade apply --mission .gitagent/missions/MSN-9001.upgrade-vX.Y.Z.yaml
# Legacy: gantry upgrade --apply --mission … and --dry-run still work
gantry doctor

MCP: gxt_upgrade_plan / gxt_upgrade_apply (same gates as CLI).

LayerMechanismOpenGantry repo
Context.cursor/rules/ + AGENTS.md + sessionStart hookRULES + MANIFEST + pinned mission
LegislationMCP gxt_draft_legislation / gxt_execute_legislationTwo-step chat approval before file write
Shell guardbeforeShellExecution (fallback)Blocks casual writes to foreman / RULES; asks on raw gantry legislate
Terminal envgxt-runtime-env.sh / gxt-pin-mission.shSets GXT_* for integrated terminal
Process trapgantry runtime execHeadless Cursor CLI / CI only

IDE Agent Write/Edit is advisory TMVC — stay within pinned mission scope; use runtime exec when you need manifest-enforced subprocess boundaries. See INTEGRATIONS Enforcement boundary.

Zero-trust gates (sole trust boundary)

All code changes are untrusted — human-typed or IDE-generated. OpenGantry does not deterministically attribute offline IDE provenance. Security relies on result-state checks only:

CheckWhenMechanism
Mission gategantry verifyMission gate_command + optional gate_success_substring + optional gate_adapter (generic default, eslint, tsc — ADR-0041). Optional phases: policy (ADR-0042), dependencies (ADR-0044).
Unit testsgate / CInpm test (node:test)
Compile / typesbuild / CInpm run build (tsc)
Manifest shapeCI / pre-pushgantry check, validate-gxt.sh manifest
Changed-code qualityPR / pre-pushscripts/check-changed-code.sh — complexity, import layers, line budgets on touched src/cli/**/*.ts
Banned importsmission gate / surgeongantry check-imports
KPI thresholdsverify (optional)gantry scan + mission kpi_gate
Perimeter (CI)PRgantry perimeter --ci on protected governance paths
Trace mappingverifyVerbatim quotes from EXECUTOR_LOG.md for mission PASS rows

Feature-mission adapter gates (ADR-0041)

After MSN-0193, whole-tree src/cli ESLint is green. Standard gantry feature missions MAY use a single explicit adapter for autonomous repair via gantry context-feed --json:

Workgate_adaptergate_command
Typestscnpx tsc --noEmit --pretty false
Linteslintnpm run lint:json (eslint --format json src/cli/**/*.ts)

Do not concatenate tsc and eslint in one gate_command. Routing is never inferred from the command string; one mission has one adapter. Typed adapters fail closed (GXT_GATE_ADAPTER_MISCONFIG) when gate_command contains unquoted &&, ||, ;, or line break — wrap sequences in a script or use gate_adapter: generic. npm run lint stays the human-readable formatter and is not adapter input.

Before pinning a tsc or eslint mission, run gantry doctor or MCP gxt_doctor (gate_adapter, adapter_baseline) (and gantry doctor --adapter-baseline / adapter_baseline: true for pre-existing debt). Preflight checks npx plus local typescript/eslint packages, tsconfig.json parseability, ESLint config, and that lint:json / the mission gate_command emit eslint --format json. Selection uses declared gate_adapter (or --gate-adapter / the MCP gate_adapter arg); doctor never sniffs gate_command.

PR CI remains changed-file ESLint (scripts/check-changed-code.sh). Whole-tree lint:json is a mission gate, not a validate/CI change. Jest/Vitest adapters require an ADR-0041 amendment before any CLI parser.

External IDE skill packs are edge-only (local, gitignored). They must not be wired into .gitagent/ or shipped integration templates. Optional [SKILL-EXEC] lines in EXECUTOR_LOG.md are human triage context only — not verify evidence. See AGENTS.md and .gitagent/planner/RUNTIME.md.

Before push / PR

npm run validate

Runs dev-validate-core.sh (build, gantry check, validate-gxt.sh manifest via Node—no jq, tests, doctor, MCP dogfood, changed-code, MSN vs origin/main) then verify-pr-missions.sh (full gantry verify on branch-changed missions). Mission gates SHOULD use dev-validate-core.sh only—not npm run validate—to avoid verify/gate recursion.

Repo-only scripts: OpenGantry specimen dev gates (check-changed-code.sh, check-import-layers.mjs, dev-validate-core.sh, and related helpers) live under scripts/ but are not in the init asset catalog. scripts/gen-asset-catalog.mjs enforces this via REPO_ONLY_SCRIPTS in scripts/lib/asset-catalog-static.mjs — the generator fails if any repo-only script leaks into templates/integrations/asset-catalog.json.

ScriptWhy repo-only
check-changed-code.shDogfood complexity/import-layer gate on PR diff
check-import-layers.mjsLegacy import scanner; dogfood delegates to gantry arch check
check-lib-cycles.mjsSpecimen maintainability gate
dev-validate-core.sh / dev-validate.shFull validation superset for maintainers
gen-asset-catalog.mjs / gen-dogfood.mjs / gen-version.mjsBuild-time generators
validate-mcp-dogfood.shMCP integration dogfood harness
release-gate-publish.sh / poll-npm-version.shnpm publish orchestration

Adopters receive scripts/validate-gxt.sh, scripts/gxt-manifest-lib.mjs, and runtime helpers from the init catalog — not the specimen-only gates above.

Hooks (automatic when core.hooksPath=.githooks):

  • pre-push — gantry verify --pre-push for branch-changed missions (legislative stubs pass after git-proof); gantry check if manifest/skills changed; advisory gantry perimeter when governance files change; changed-code gate for touched src/cli/**/*.ts.

Releasing

A release is its own mission that bumps every version site (including packages/opengantry-cage/package.json) and turns the changelog’s draft row into the release row (MSN-0246, MSN-0256). After it merges:

git tag -a v<version> <merge-commit> -m "OpenGantry v<version>"
git push origin v<version>

The tag runs npm-publish.yml, which publishes @jeger-ai/opengantry and then @jeger-ai/opengantry-cage. Each publish goes through scripts/npm-publish-if-new.sh and is skipped when that version is already on npm.

  • Authentication is npm trusted publishing (MSN-0259). npm exchanges the job’s GitHub OIDC token for a short-lived publish token and attaches provenance; the workflow holds no npm token or secret and needs npm 11.5.1 or newer, which it checks. Each package needs a trusted publisher on npmjs.com, under the package’s settings: provider GitHub Actions, repository jeger-ai/opengantry, workflow npm-publish.yml. A package without one fails to publish with an auth error (E404, ENEEDAUTH, E401 or EOTP), and the script names the missing setting; there is no token to fall back to.
  • New packages: trusted publishing can only be configured for a package that exists, so the first version of a new package is published by hand (below), then the trusted publisher is added before the next release.
  • Once releases publish from CI: set both packages to disallow tokens on npmjs.com and delete any NPM_TOKEN repository secret.
  • Partial release: if one package published and the other failed, do not re-run the tag run (it uses the workflow file at the tagged commit) and never move the tag. Fix the cause, then start a run from main with the tag. It refuses unless the tag matches package.json and everything that gets published on main is identical to the tag; the package already on npm is skipped. Provenance for that run names the main commit it used.
gh workflow run npm-publish.yml -f tag=v<version>
  • Emergency local publish (no provenance): from a clean checkout of the tag, after npm run build, run npm publish --access public for the root package or add --workspace @jeger-ai/opengantry-cage. publishConfig no longer forces provenance, so this works outside CI; npm asks for two-factor approval in the browser or for --otp <code>.

LLM evidence + KPI gate

Nondeterministic LLM checks produce committed evidence; merge stays deterministic:

  1. Mission declares optional llm_verifiers + kpi_gate (see .gitagent/planner/MISSION.schema.yaml).
  2. Executor runs gantry scan --mission … — each verifier command must print JSON on stdout (trailing whitespace/newlines tolerated). Success contract per verifier:
    • process exit code 0
    • parseable JSON object with a non-empty metrics map (Record<string, number | boolean>)
    • optional findings array and exit_code field in JSON (fragment exit_code can raise report exit_code even when the process exits 0)
    • failed optional verifiers set {id}::__verifier_ok: false in the committed report; required verifiers fail the scan when the contract is not met
  3. gantry verify runs shell gate_command, then evaluates kpi_gate.thresholds against .gitagent/kpi/MSN-NNNN.json, then trace mapping.
  4. KPI stale binding mirrors trace evidence: local warnings only; --pre-push / --ci fail-closed when TMVC drifts after the report commit.
  5. Advisory rubric findings (ARCHITECTURE_RUBRIC, PERFORMANCE_RUBRIC) surface as kpi_warnings and structured findings[] on verify PASS — they never flip FAIL→PASS. See ADR-0025 and ADR-0035. Example stub: examples/performance-judge/.
  6. gantry register <dir> proposes skills from AST footprints; Planner still owns manifest edits (Rule 4.4).
  7. gantry perimeter --ci in CI enforces verified signatures on protected paths (local mode is advisory).

See ADR-0020.

Code Surgeon (verify --fix, MSN-0047+)

Deterministic quarantine mutations for specific gate failures — an isolation layer, not a pass generator:

  1. gantry verify --fix (interactive or --non-interactive) invokes a registered Code Surgeon when gate output matches a known failure:
    • GXT_BANNED_IMPORT_DETECTED — gantry check-imports stderr
    • GXT_IMPORT_LAYER_VIOLATION — check-import-layers.mjs --json structured report
  2. Surgeon quarantines the offending import via TypeScript AST: removes the live import declaration, injects GXT-SURGEON-QUARANTINE markers and lazy Proxy roadblocks (no silent deletion).
  3. On mutation, append [SURGEON-MUTATION] … to EXECUTOR_LOG.md, then rerun the full verify pipeline with --fix disabled.
  4. Plain gantry verify (no --fix) remains fail-closed and never mutates TMVC.

Language boundary: verify core phases (git_proof, gate, kpi, trace) are language-agnostic. Native surgeons require TypeScript in the adopter workspace (or gantry package root); unresolved typescript disables surgeon paths only — core verify still runs.

Import-layer JSON contract: node scripts/check-import-layers.mjs --json <files…> emits { schema_version, ok, violations[] } with stable rule_id values. Surgeons parse JSON only — not human stderr.

Implementation: src/cli/lib/surgeons/ registry + orchestration in surgeon-orchestration.ts / verify-present.ts.

Release: see CHANGELOG.md § Maintainers for npm publish and release-squash policy.

Definition of done (OpenGantry repo)

  • Mission under .gitagent/missions/ with Planner [MSN-…] commit when GXT paths or behavior changed
  • EXECUTOR_LOG.md trace lines match mission PASS rows (or gantry verify on your mission passes)
  • npm run validate passes
  • Rule 4.4: manifest skill keys ↔ skills/*.md in the same change set when skills change
  • .gitagent/ARCHITECTURE.pointer.json resolved and layer rules respected for src/cli edits
  • When changing agent integration surfaces: bump templates/integrations/compatibility.json verified_date + recipe fragments in the same PR

CI parity

Pull requests run .github/workflows/gxt-validate.yml:

JobWhat it enforces
pr_governancePRs must target the integration branch (main here; default_branch in init template; override via repo variable GXT_INTEGRATION_BRANCH) — blocks stacked mission PRs
manifestgantry check, validate-gxt.sh manifest, unit tests, gantry doctor, gantry perimeter --ci on PRs
code_qualityChanged-code gates (check-changed-code.sh) on PR diff
msn_commits[MSN-NNNN] on commits touching MSN-enforced paths (substrate + MANIFEST tmvc_roots); or valid gxt-bypass note; or repository trusted_automation policy (gxt-manifest-lib.mjs eval-commit)
mission_verifyMission purity (one [MSN-NNNN] per ${base}..${head}); full gantry verify on each mission file in triple-dot diff; fails if protected paths change without a mission file unless trusted_automation policy passes (eval-range)

Local npm run validate is the full superset (includes verify-pr-missions.sh + MSN vs origin/main). Run it before you open a PR.

gantry init ships scripts/verify-pr-missions.sh when CI is enabled. Existing installs: run gantry upgrade apply or re-init managed CI assets.

Troubleshooting verify / hooks

  • Run the CLI from the repo root: npm run gantry -- verify --mission .gitagent/missions/<file>.yaml (after npm run build). Policy failures print a one-line error plus Fix: remediation hints — not stack traces.
  • Set GANTRY_DEBUG=1 only when you need a stack trace for an unexpected error.

Smoke checklist (this repo)

  1. npm run build && npm test
  2. ./scripts/check-changed-code.sh origin/main HEAD (when you changed src/cli)
  3. gantry doctor → exit 0 with warnings allowed
  4. Formatter drift: gantry verify passes without --fuzzy-trace
  5. gantry metrics --json identical on two consecutive runs at same ref (including gxt_extension_metadata)

Code quality (changed files only)

PRs run ./scripts/check-changed-code.sh <base> <head> (also npm run check:changed locally against origin/main).

Deterministic documentation gates (repo-wide, not diff-scoped) run in dev-validate-core:

  • ./scripts/assert-docs-deterministic.sh — published doc inventory, docs/index.md link integrity, doc-surface legacy naming drift

  • ./scripts/assert-no-stale-cli-naming.sh — implementation-path legacy naming drift (src/, scripts/, hooks, examples)

  • ESLint complexity and function length on touched src/cli/**/*.ts

  • Import layer rules (lib must not import commands, etc.)

  • File line budgets for non-grandfathered paths (see docs/ARCHITECTURE.md)

If CI fails:

npm run lint -- path/to/changed.ts
./scripts/check-changed-code.sh origin/main HEAD