Skip to content
Open
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
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Reviewers requested automatically on every pull request.

* @swapnilpaliwal-sd @JaredHLZhang @suyashpaliwal26 @Whua689
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
blank_issues_enabled: false
52 changes: 52 additions & 0 deletions .github/ISSUE_TEMPLATE/engine-defect.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Engine defect
description: A call edge the engine resolves wrongly, or does not resolve at all.
labels: [bug, engine]
body:
- type: markdown
attributes:
value: |
**Describe the defect with a minimal synthetic example.** Do not paste code from
a real project, name the project, or quote its identifiers or file names — this
tracker is a durable public record, and naming someone's codebase reads as a
benchmark claim about it. Reduce the finding to `class Widget`, `pkg/_helpers.py`
and the like, and describe the mechanism.

- type: dropdown
id: language
attributes:
label: Front end
options: [java, python, typescript]
validations: { required: true }

- type: textarea
id: repro
attributes:
label: Minimal example
description: The smallest synthetic source that shows it. Include the call site.
render: text
validations: { required: true }

- type: textarea
id: expected
attributes:
label: What the engine should resolve, and what it resolves instead
description: |
Name the edge both ways — `A.foo -> B.bar`, versus the `ambiguous_unknown` or
wrong target you actually get.
validations: { required: true }

- type: textarea
id: mechanism
attributes:
label: Mechanism
description: |
What does the rule join on, and why does the join fail? If you already know which
relation derives zero rows, say which.
validations: { required: false }

- type: input
id: parser
attributes:
label: Parser commit
description: The SHA in `.github/parser-ref` you reproduced against.
validations: { required: false }
25 changes: 25 additions & 0 deletions .github/ISSUE_TEMPLATE/enhancement.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Enhancement
description: Resolution power the engine does not have yet.
labels: [enhancement]
body:
- type: dropdown
id: language
attributes:
label: Front end
options: [java, python, typescript, all]
validations: { required: true }

- type: textarea
id: construct
attributes:
label: The construct
description: A synthetic example of the code shape that does not resolve today.
render: text
validations: { required: true }

- type: textarea
id: why
attributes:
label: Why it matters
description: What becomes answerable once this resolves. Mechanism, not corpus.
validations: { required: true }
26 changes: 26 additions & 0 deletions .github/ISSUE_TEMPLATE/harness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Test harness or CI
description: A suite, golden, oracle or workflow that measures the wrong thing.
labels: [test]
body:
- type: markdown
attributes:
value: |
A measurement fault outranks an engine fault: while it stands, every number the
suite reports is unsafe to act on. Say what the harness claims and why that claim
is not true.

- type: textarea
id: claim
attributes:
label: What the harness reports, and why it is wrong
validations: { required: true }

- type: textarea
id: isolate
attributes:
label: How you isolated it
description: |
The rows you read, the A/B you ran, the commit on both arms. A regression that
was really the harness looks identical to one that was really the engine until
this part exists.
validations: { required: true }
42 changes: 42 additions & 0 deletions .github/ISSUE_TEMPLATE/parser-blocked.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Parser-blocked
description: The engine cannot resolve something because the IR does not carry it.
labels: [parser-blocked]
body:
- type: markdown
attributes:
value: |
Use this when the fix does not belong in this repository. The engine's rules are
read-only with respect to the parser: a missing fact is filed here, proven, and
fixed in `AxiomCodeAI/parser`.

**A parser defect needs three things before it is one:** the parser source that
drops the fact, a synthetic input that reproduces it, and the real construct it
came from. Without all three this is a hypothesis.

- type: dropdown
id: language
attributes:
label: Front end
options: [java, python, typescript]
validations: { required: true }

- type: textarea
id: missing
attributes:
label: The fact that is missing
description: Which relation, which column, and what it should contain.
validations: { required: true }

- type: textarea
id: evidence
attributes:
label: Evidence in the parser source
description: The file and function that drops it. Synthetic example only.
validations: { required: true }

- type: textarea
id: blocked
attributes:
label: What this blocks here
description: The rule that cannot be written, or the edge that cannot resolve.
validations: { required: true }
23 changes: 23 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
version: 2
updates:
# Actions are pinned by major tag; this keeps them current and, more to the
# point, surfaces the deprecations that otherwise fail CI without warning.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
labels: [build, dependencies]
open-pull-requests-limit: 3

- package-ecosystem: npm
directory: /
schedule:
interval: weekly
labels: [build, dependencies]
open-pull-requests-limit: 3
# The engine's behaviour is pinned to the toolchain that produced the
# goldens. A TypeScript major arriving on its own schedule is a change to
# what this repo measures, not a routine bump.
ignore:
- dependency-name: typescript
update-types: [version-update:semver-major]
40 changes: 40 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!--
Every change here follows: issue first, then a pull request that closes it.
If there is no issue yet, open one — the issue is where the measurement that
found the problem lives, and this is where the fix lives.
-->

Fixes #

## What changed

<!-- The mechanism. What the rule now joins on, or what the harness now measures. -->

## Why the goldens moved, or why they did not

<!--
Required whenever test/*/expected changed. A golden diff is a change in resolution
power and has to be readable as one: which edges appeared, which disappeared, and
why each is correct now.

If nothing under test/*/expected changed, say so — "no golden moved" is a real and
useful claim about a rules change.
-->

## Evidence

<!--
Synthetic examples only. Never name the project a defect was found in, quote its
identifiers, or paste its code — not here, not in the issue, not in the commit
message.

Keep the corpus measurement out of the prose too: describe the defect by mechanism,
not by how many links it cost.
-->

## Checklist

- [ ] The three suites pass locally against the parser commit in `.github/parser-ref`
- [ ] Any golden that moved is explained above
- [ ] A fix validated on more than one shape, so this is not overfitting to one case
- [ ] Labels set, including the front end (`java` / `python` / `typescript`)
99 changes: 99 additions & 0 deletions .github/scripts/protect-main.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# Apply the branch ruleset for `main`.
#
# bash .github/scripts/protect-main.sh # apply
# bash .github/scripts/protect-main.sh --dry-run # print the payload only
#
# Idempotent: updates the ruleset if it exists, creates it otherwise.
#
# What it enforces
# - no direct push to main, and no force-push
# - main cannot be deleted
# - every change arrives by pull request, with an approving review
# - a review is dismissed when new commits are pushed
# - review from a code owner
# - the `CI` check must pass, evaluated against an up-to-date branch
# - linear history: squash or rebase, no merge bubbles
#
# `bypass_actors` is empty on purpose. A rule that some accounts can step around
# is a convention rather than a control, and the point of this file is the control.
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail

REPO="${REPO:-AxiomCodeAI/axiom-code-graph}"
DRY=0
[ "${1:-}" = "--dry-run" ] && DRY=1

payload="$(cat <<'JSON'
{
"name": "protect-main",
"target": "branch",
"enforcement": "active",
"conditions": {
"ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] }
},
"bypass_actors": [],
"rules": [
{ "type": "deletion" },
{ "type": "non_fast_forward" },
{ "type": "required_linear_history" },
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 1,
"dismiss_stale_reviews_on_push": true,
"require_code_owner_review": true,
"require_last_push_approval": false,
"required_review_thread_resolution": true,
"allowed_merge_methods": ["squash", "rebase"]
}
},
{
"type": "required_status_checks",
"parameters": {
"strict_required_status_checks_policy": true,
"do_not_enforce_on_create": false,
"required_status_checks": [
{ "context": "CI" }
]
}
}
]
}
JSON
)"

if [ "$DRY" = "1" ]; then
echo "$payload"
exit 0
fi

# Fail with something readable rather than a raw API error.
if ! gh api "repos/$REPO/rulesets" >/dev/null 2>&1; then
echo "cannot read rulesets on $REPO — check that this account has admin rights there" >&2
exit 1
fi

existing="$(gh api "repos/$REPO/rulesets" --jq '.[] | select(.name=="protect-main") | .id' || true)"

if [ -n "$existing" ]; then
echo "updating ruleset $existing on $REPO"
printf '%s' "$payload" | gh api -X PUT "repos/$REPO/rulesets/$existing" --input - >/dev/null
else
echo "creating ruleset on $REPO"
printf '%s' "$payload" | gh api -X POST "repos/$REPO/rulesets" --input - >/dev/null
fi

# Merge-method hygiene lives on the repository, not the ruleset: squash-only, and
# delete the branch once it has landed so the branch list stops accumulating the
# stale aliases this repo has collected before.
gh api -X PATCH "repos/$REPO" \
-F allow_squash_merge=true \
-F allow_merge_commit=false \
-F allow_rebase_merge=false \
-F delete_branch_on_merge=true \
-F allow_auto_merge=true >/dev/null

echo "done. main now requires a pull request and a green CI check."
gh api "repos/$REPO/rulesets" --jq '.[] | " ruleset: \(.name) enforcement=\(.enforcement)"'
73 changes: 73 additions & 0 deletions .github/scripts/run-suite.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# Run one engine regression suite under CI semantics.
#
# The suites are written for a developer laptop, where "the parser isn't built
# yet" is a good reason to step aside: they print SKIP and exit 77. On CI that is
# the one outcome that must never be tolerated. A gate that opens when its input
# is missing is worse than no gate at all — the pull request goes green having
# tested nothing, and the next person reads that green as evidence.
#
# So this wrapper turns every shape of "did not actually run" into a failure:
#
# 1. exit 77 from the suite itself;
# 2. a `SKIP: parser not found` line from a sub-harness (torture, fixtures)
# that caught its own 77 and carried on;
# 3. a suite that reported zero passing cases, which means the case loop found
# nothing to do and the exit status is meaningless.
#
# Usage: run-suite.sh <java|python|typescript|javascript> [extra args passed to the suite]
# ─────────────────────────────────────────────────────────────────────────────
set -uo pipefail

lang="${1:?usage: run-suite.sh <java|python|typescript|javascript> [args...]}"; shift
root="$(cd "$(dirname "$0")/../.." && pwd)"
suite="$root/graph/test/$lang/run-tests.sh"

[ -f "$suite" ] || { echo "::error::no suite at $suite"; exit 1; }

# The parser lives in this repository (parser/) and `npm run build` builds it; the
# suites default AXIOM_PARSER to parser/dist/index.js. CI still refuses to start on a
# missing build rather than discover the absence halfway through as a SKIP.
AXIOM_PARSER="${AXIOM_PARSER:-$root/parser/dist/index.js}"; export AXIOM_PARSER
if [ ! -f "$AXIOM_PARSER" ]; then
echo "::error::AXIOM_PARSER=$AXIOM_PARSER does not exist. The parser did not build (npm run build)."
exit 1
fi

log="$(mktemp)"
echo "── $lang suite ── parser: $AXIOM_PARSER"
bash "$suite" "$@" 2>&1 | tee "$log"
rc="${PIPESTATUS[0]}"

if [ "$rc" -eq 77 ]; then
echo "::error::the $lang suite skipped itself (exit 77). A skipped suite is a failed gate."
exit 1
fi

# A sub-harness that swallowed its own 77. The suite's exit status cannot see this:
# it counted the sub-harness as neither a pass nor a failure, so the run is green
# with a whole family unexecuted.
#
# EVERY skip is a failure here, not just the parser ones. The sub-harnesses decline
# for several different reasons — no parser, no javac, a JDK without
# java.lang.classfile (the torture oracle needs 24+), no oracle checkout — and each
# one is a dependency CI is supposed to provide. If a skip is ever legitimate it
# should be an explicit exclusion in this file, visible in a diff, rather than a
# green run that quietly tested less than the last one.
if grep -qE '(^|[[:space:]])SKIP([: (]|PED)' "$log"; then
echo "::error::a sub-harness in the $lang suite skipped instead of running:"
grep -nE '(^|[[:space:]])SKIP([: (]|PED)' "$log" | sed 's/^/ /'
echo "::error::CI must supply what it wanted. Do not relax this check to go green."
exit 1
fi

# "passed 0" means the case loop matched nothing — a rename or a bad filter, not a
# clean run. Guard it, because exit 0 with zero assertions is the quietest failure
# this harness can produce.
if grep -qE '^passed:? 0([^0-9]|$)|^passed:? 0,' "$log"; then
echo "::error::the $lang suite passed 0 cases — it asserted nothing."
exit 1
fi

exit "$rc"
Loading
Loading