Skip to content

Define the shared GraphIR, source identity, and navigation contracts (MOO-68) - #68

Closed
OwenTanzer wants to merge 26 commits into
braedonsaunders:mainfrom
OwenTanzer:otmooper12/moo-68-define-the-shared-graphir-source-identity-and-navigation
Closed

Define the shared GraphIR, source identity, and navigation contracts (MOO-68)#68
OwenTanzer wants to merge 26 commits into
braedonsaunders:mainfrom
OwenTanzer:otmooper12/moo-68-define-the-shared-graphir-source-identity-and-navigation

Conversation

@OwenTanzer

Copy link
Copy Markdown

Summary

Implements MOO-68's full 7-commit checklist: the versioned contract layer (src/graph-ir/) that MOO-69 (repository adapter), MOO-70 (pyan3 file layer), and MOO-71 (CodeVisualizer function layer) will all build against. Pure library modules + fixtures + docs — no application wiring, no pyan3/CodeVisualizer adapters (both explicitly out of scope for this issue).

  • Commit 1sourceCoordinate.js: canonical SourceCoordinate (repository identity, resolved revision, path, symbol scope chain, kind, range, ambiguity flag), structured JSON serialization, base64url route/cache tokens. Cross-platform path normalization.
  • Commit 2githubContext.js: normalizes repository/branch/commit/PR requests into one AnalysisContext always pinned to a resolved SHA. assertContextPropagation blocks a drill-down request from silently switching revisions relative to its parent graph.
  • Commit 3graphIR.js: the shared, versioned GraphIR envelope (nodes/edges/groups, analyzer provenance, confidence, warnings, rendering hints) all three layers validate against, while keeping distinct kind/hint vocabularies per layer. Rejects cross-layer node/edge mismatches and dangling edges with specific messages; ignores unknown extra fields anywhere in the tree.
  • Commit 4adapterResult.js: AdapterResult envelope + fixed 8-category ErrorCategory set. sanitizeDiagnostic strips stack traces and redacts secret-shaped keys at any depth, applied unconditionally.
  • Commit 5navigation.js: createSelectionEvent/createDrillDownEvent/createOpenSourceEvent, gated by isDrillDownEligible so an ambiguous/unresolved coordinate can never dispatch an incorrect drill-down. NavigationHistory back/forward breadcrumb stack. This is the real dispatch MOO-67 Commit 4E's node-activate no-op seam was left for.
  • Commit 6cacheKey.js: buildCacheKey (stable sha256 over normalized context/analyzer/schema/coordinate/depth/options — equivalent requests collapse to one key, any real difference never collides), isCacheStale, buildProvenanceSummary.
  • Commit 7src/graph-ir/index.js barrel, representative fixtures for all three layers (tests/fixtures/graph-ir/*.json, regenerable via scripts/gen-graph-ir-fixtures.mjs), a runnable example adapter (examples/minimal-graphir-adapter.mjs) proving GraphIR can be produced/consumed with zero application-code imports, and docs/graph-ir-contract.md documenting the full repository → file → function identity flow and extension rules.

Test plan

  • node --test tests/*.test.mjs — 207/207 (132 pre-existing + 75 new)
  • npm run build — clean
  • node examples/minimal-graphir-adapter.mjs runs end-to-end with no application-code imports
  • All three fixtures validate against validateGraphIR

🤖 Generated with Claude Code

OwenTanzer and others added 26 commits July 21, 2026 07:48
Document the pinned starting revision, startup commands, and known
nondeterministic fields, and extend the existing repo-smoke script
with committed structural snapshots for all four fixtures so later
modularization commits can prove they preserved analyzer behavior
rather than assert it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Stand up dev/build/preview commands over the existing index.html
unchanged, plus a minimal static server entry point serving the
production build. Analyzer marker extraction and the app's inline
script are untouched — this only proves the tooling works, so the
existing single-file index.html stays available as a rollback path
until module extraction lands in Commit 3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… Commit 3)

Move Parser, buildAnalysisData, calcBlast, calcHealth, runAnalysisData,
createAnalysisWorkerSource, GitHub, and the rest of the analyzer's
top-level names out of index.html's inline script into src/analyzer.js,
a real ES module. index.html bridges its exports onto window for the
still-classic app script; card/lib/analyzer.js and every Node test now
import it directly instead of VM-extracting a marker-delimited block
from index.html text.

The analysis Web Worker's bootstrap (createAnalysisWorkerSource) used
to fetch the page's own HTML and slice out the analyzer by string
marker; it now fetches its own module URL via import.meta.url instead,
with new internal CODEFLOW_CORE_START/END markers excluding
runAnalysisData/createAnalysisWorkerSource themselves from what gets
embedded into the worker (their source contains import.meta, which is
a syntax error in the classic-script worker context). Verified
end-to-end via headless Chromium against both the dev server and a
production build.

Also folds two previously-duplicated stub functions
(getSecurityScanContent, isSanitizedPreviewRenderer) into the real
module, closing a latent behavioral gap between what the browser ran
and what Node-side tests exercised.

Known regression, documented in docs/baseline.md rather than silently
carried: opening index.html directly via file:// now crashes, since
Chromium blocks the analyzer module's import under CORS for that
origin. The app still works correctly served over npm run dev or
npm run build + npm start.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
card/lib/analyzer.js's synchronous require(esm) of src/analyzer.js
needs unflagged require(esm) support, which shipped in Node 20.19.0
and 22.12.0 specifically -- not all of card/'s previously-declared
">=20" range, and not 21.x (non-LTS, EOL before the backport). Narrow
both package.json engines fields to "^20.19.0 || >=22.12.0" (matching
Vite 8's own declared constraint) and add engine-strict=true via
.npmrc in both locations so an incompatible local Node fails loudly
at install time instead of a later ERR_REQUIRE_ESM.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Product decision: accept file:// support's removal (flagged in Commit
3) rather than restore it. MOO-67 is building toward a server-backed
Railway application with server-held credentials; preserving
double-click local-file execution would complicate the modular
architecture for little future value.

docs/baseline.md now records this as resolved rather than open.
README.md's Quick Start, Architecture, Contributing, and FAQ sections
no longer advertise the zero-install "just open index.html" workflow
-- self-hosting now documents npm install + npm run dev (or npm run
build && npm start) as the supported path. Also fixed two other
now-stale README claims caught while editing this section: the
Architecture diagram's "Single File" label, and the Contributing
section's test command (node --test tests/ doesn't discover this
repo's flat tests/*.test.mjs layout; needs the glob).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add tests/ui-smoke.mjs: six deterministic Playwright checks covering
the repository-view behaviors Commit 4B-4E are about to touch --
local-folder load + graph render, node-click detail-panel update,
visualization-type switching, ?repo= URL prefill without a network
call, browser back/forward, and zero real console errors. Verified
passing against both the dev server and a production build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move the ~150-line D3 force-graph build/update lifecycle out of
App()'s inline useEffect into renderRepositoryGraph()
(src/render/repositoryGraph.js), a real ES module bridged onto window
the same way src/analyzer.js is. Mechanical extraction (sed-sliced,
not retyped): svgRef.current -> svgEl, setTooltip -> onHover,
setSelected/setBlastRadius on background click -> onBackgroundClick,
everything else (data, colorMap, colorMode, theme, folderFilter,
graphConfig, COLORS, LAYER_COLORS) became explicit parameters.

zoomRef/simRef/linksRef/nodesRef/selectFileRef are passed through as
the same ref objects App() already holds -- several other call sites
(zoom controls, blast-radius reset, PDF export, "Back to Issues") read
these directly and needed no changes as a result.

Verified via tests/ui-smoke.mjs (6/6 against both dev and production
builds) plus an ad hoc Playwright probe specifically exercising zoom
in/out/reset, hover tooltip, and "Back to Issues" -- zero console
errors, all three still worked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Move selected, blastRadius, rightTab, and drillDown -- the state the
repository view's selection and detail panel need -- from four
separate useState calls in App() into useRepositorySelection()
(src/state/selection.js), bridged onto window the same way as
Commits 3 and 4B.

Every variable name stayed identical, so no call site elsewhere in
App() needed to change. Deliberately left alone: showGraphConfig,
rightPanelWidth, folderFilter, data/loading/error, theme, and all
architecture/security-specific state, plus expandedPaths/
expandedCards (unrelated tree-expansion state that happened to sit
textually nearby).

Verified via tests/ui-smoke.mjs (6/6) plus an ad hoc Playwright probe
cycling all four panel tabs -- zero console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Consolidate buildAppUrl and the scattered query-param read/write
logic (a mount-time useEffect reading ?repo=/?run=1, and two
window.history.replaceState call sites) into src/state/route.js:
buildRepoUrl, readRouteRepo, writeRepoRoute, clearRoute. Bridged onto
window the same way as Commits 3, 4B, and 4C.

Scoped to repository identity only, per the checklist -- not active
view/panel/selection restoration, and not canonical source
coordinates or breadcrumb payloads, both reserved for MOO-68.

buildRepoUrl/readRouteRepo accept an optional baseHref/search
parameter (defaulting to the real window.location), making this the
first of the four extractions genuinely unit-testable without a DOM --
tests/route-state.test.mjs adds 8 tests covering URL construction, the
run=1 gate, and the three validation guards the original inline code
already had.

Verified via tests/ui-smoke.mjs (6/6, its route/hash-restoration check
exercises readRouteRepo directly) plus an ad hoc Playwright probe
calling writeRepoRoute/clearRoute directly, since the write path only
triggers on a real GitHub load the smoke suite deliberately avoids.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… 4E)

Add a dblclick handler on graph nodes in repositoryGraph.js alongside
the existing click handler, using the same node identity (d.id) the
click handler already uses. Wired to a new activateFileRef (same
pattern as selectFileRef), which App() initializes to a no-op and
does not wire to anything else -- node-activate may remain unused or
resolve to a no-op in MOO-67 per the checklist, with real drill-down
semantics reserved for MOO-68.

This is the last piece of Commit 4 -- 4A (UI smoke suite), 4B
(repository graph renderer), 4C (selection/panel state), 4D (route
persistence), and now 4E (interaction seam) are all landed and
independently verified.

Verified via tests/ui-smoke.mjs (6/6) plus an ad hoc Playwright probe
double-clicking a node (no crash) then single-clicking it again
(still selects correctly, proving no event-wiring corruption).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restructure server/index.js from the Commit 2 static-file placeholder
into a real server shell: config validated fail-fast at startup
(server/lib/config.js), a request-scoped workspace abstraction with a
single controlled root and cleanup (server/lib/workspace.js),
structured per-request JSON logging with sanitized secrets
(server/lib/logger.js), distinct liveness/readiness endpoints
(server/lib/health.js), and a bounded POST /api/analyze endpoint
(server/routes/analyze.js) that only reaches paths already on the
server's own filesystem -- no GitHub credential or auth gate exists
yet, that's Commit 6.

analyzer-bridge.js reuses card/lib/collect.js's buildAnalyzed() for
file collection rather than writing a fourth copy of that logic.

Added durable automated coverage: 9 new unit tests
(server-config.test.mjs, server-workspace.test.mjs) plus
tests/server-smoke.mjs, which spawns the real server process and
confirms /api/analyze against golden-world matches the exact
files:6/functions:7/connections:6 baseline from Commit 1, path-
traversal and missing-path requests are rejected, and the workspace
root is completely empty after all requests (cleanup verified, not
just callable).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GitHub.scanTree/scanRecursive call shouldExcludeFile/
shouldIgnoreDirectory, which were left behind in index.html during
the Commit 3 extraction -- they only worked in the browser by
accident (window-fallthrough), and threw
"shouldExcludeFile is not defined" the first time anything called
GitHub.scan() server-side, ahead of building Commit 6's GitHub-backed
endpoint around it.

Move IGNORE, normalizeExcludePath, matchesExcludePattern,
shouldIgnoreDirectory, and shouldExcludeFile into src/analyzer.js for
real, exported and re-bridged onto window so index.html's own
local-folder-reading code (which also calls these) keeps working
unchanged.

Verified GitHub.scan/getFile against a real public repo from Node.
Added tests/analyzer-module.test.mjs (4 tests, no network) so this
gap can't silently reappear. Full suite 83/83, clean build, and
tests/ui-smoke.mjs (6/6) all pass -- pure addition/relocation, no
behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three independent gates now sit in front of every /api/* route:
- server/lib/auth.js -- a shared-secret Authorization: Bearer check,
  timing-safe compared. /healthz/readyz/static stay public for
  Railway's own health monitoring.
- server/lib/rate-limit.js -- in-memory per-client-IP fixed-window
  limiter (RATE_LIMIT_PER_MINUTE).
- server/lib/validate-repo-request.js + server/lib/allowlist.js --
  owner/repo/ref/PR format validation, then an allowlist check, both
  before any GitHub call.

AUTH_TOKEN, GITHUB_TOKEN, and at least one of ALLOWED_REPOS/
ALLOWED_OWNERS are now required, validated fail-fast at startup like
everything else in config.js.

New POST /api/analyze-repo (server/routes/analyze-repo.js +
server/lib/github-analyzer-bridge.js) fetches a repo at a resolved
ref (default branch, explicit branch/commit, or a PR's head SHA) via
the GitHub REST API using the server-held token, then runs it through
the same analyzer everything else uses. Reuses GitHub/Parser/
shouldExcludeFile/buildAnalysisData from src/analyzer.js rather than
writing a second GitHub client -- but not GitHub.scanTree/getFile
as-is, since both are hardcoded to the default branch with no ref
parameter, which is exactly the gap this commit needs closed.

Found and fixed two real bugs while verifying against real GitHub
data (not fixtures): a PR's head commit usually lives in a fork, not
the base repo, so tree/blob fetches need to follow head.repo, not the
originally-requested owner/repo; and GitHub.request()'s errorMap
errors are plain Errors, not GithubFetchError, so a genuinely-expected
failure (a deleted PR fork) was surfacing as a generic 500 instead of
a clean 502 with GitHub's own message.

Added tests/server-auth.test.mjs (16 unit tests), expanded
server-config.test.mjs (10 tests) and server-smoke.mjs (17 steps,
including both bugs above verified against real repos/PRs via a real
GitHub credential from `gh auth token`). Full suite 105/105, clean
build, tests/ui-smoke.mjs still 6/6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add railway.json pinning the build/start commands and wiring
Railway's own rollout health gate to /readyz (not just "process is
up") -- confirmed applied by re-checking deployment metadata before
and after. Railway's Railpack builder had already auto-detected and
deployed this successfully with zero config, but explicit
configuration is a real checklist item, not just a nice-to-have.

Created and linked the codeviz Railway project/service, set
AUTH_TOKEN (generated, not committed anywhere), GITHUB_TOKEN (the
same PAT already used throughout Commits 5-6), ALLOWED_OWNERS
(OwenTanzer), and NODE_ENV=production via `railway variable set`.

Verified against the live deployment, not just locally: healthz/
readyz, static serving, auth rejection (401 anonymous and wrong-
token), the local-path analyze endpoint matching the same
golden-world baseline every other environment produces, allowlist
rejection (403) for a non-allowlisted owner, and a real GitHub-backed
analysis (OwenTanzer/CodeVisualizer) succeeding end-to-end through
the deployed instance.

Documented both real rollback paths (dashboard redeploy of a specific
past deployment; CLI redeploy from an earlier git commit -- confirmed
there's no CLI command to target a non-latest deployment directly,
rather than assuming one exists) and the exact DNS/Moopertonic Hub
cutover steps for MOO-72, without performing that cutover now.

MOO-67 complete: all seven commits landed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Requested after initial deployment: analyze other users' repos, not
just the operator's own. server/lib/allowlist.js now recognizes a
literal "*" in ALLOWED_OWNERS as an explicit "any owner" opt-in,
rather than requiring individual owner names to be piled on. The auth
token remains the actual gate on who can reach /api/analyze-repo;
this only changes which repos a valid caller can point it at.

Set ALLOWED_OWNERS=* on the live codeviz Railway deployment and
redeployed. Verified octocat/Hello-World (previously blocked) now
analyzes successfully, and OwenTanzer/CodeVisualizer (the original
allowlist entry) still works.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three findings from PR #1 review, all fixed:

1. MAX_REPO_FILES capped file count but not byte size -- the
   GitHub-backed path fetched every accepted blob into memory before
   analysis, and the wildcard allowlist follow-up means any
   authenticated caller can now point the server at any public repo.
   Add MAX_FILE_BYTES/MAX_REPO_BYTES; GitHub's tree API already
   reports each blob's size, so oversized files are rejected before
   any content is fetched/decoded. Individually-oversized files are
   skipped (like an ignored directory); the aggregate is a hard cap.
   Refactored the selection logic into a pure, exported
   selectAnalyzableFiles() specifically so it's unit-testable against
   synthetic tree data instead of only reachable through a real
   GitHub round-trip (7 new tests).

2. resolveWithinRepo()'s comment claimed it rejected symlinks; it only
   did lexical path-traversal checking and never called realpath(),
   so a symlink sitting lexically inside the repo could still point
   elsewhere. Fixed by resolving both the repo root and the requested
   target through realpath() and checking containment on the
   resolved paths -- what cp() actually reads from. Verified with
   real filesystem junctions (5 new tests) rather than just asserting
   the fix works: one junction escaping the repo root is rejected,
   one pointing elsewhere inside the root is accepted.

3. /api/analyze-repo bounded its body via MAX_REQUEST_BODY_BYTES;
   /api/analyze buffered the whole request unbounded, despite both
   being publicly addressable behind the same bearer-token gate.
   Extracted one shared readJsonBody() (server/lib/http-body.js, 5
   new tests) both routes now use.

Full suite 126/126 (up from 107). tests/server-smoke.mjs re-verified
against real GitHub data (17/17) to confirm none of this broke the
existing happy paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Flagged as non-blocking by review but easy to close out now: add
.github/workflows/test.yml running npm ci && npm run build && node
--test tests/*.test.mjs on push/PR to main. Scoped to the
credential-free, no-real-network unit/integration suite --
tests/ui-smoke.mjs (needs a Playwright browser) and
tests/server-smoke.mjs (needs a real GitHub token) stay
manual-precondition scripts for now; wiring those into CI too is a
reasonable separate follow-up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3 new alerts (2 high, 1 medium) surfaced once the diff was large
enough for CodeQL to re-evaluate index.html/server/ -- pre-existing
gaps, not regressions, per GitHub's own note that alerts not
introduced by the PR can still appear when changes are large. Fixed
regardless:

- server/lib/static.js (js/path-injection, high x2): the
  resolved.startsWith(distDir + sep) check is a raw string-prefix
  comparison, not a pattern CodeQL recognizes as a proven sanitizer.
  Rewrote to use path.relative() + a ".."/absolute check, the same
  idiom server/routes/analyze.js's resolveWithinRepo already uses
  (which CodeQL did not flag). Deliberately did not add realpath():
  dist/ is build output the operator controls, and realpath() throws
  for anything not on disk, which would have broken the existing
  SPA-style fallback for unmatched client-side routes. Added
  tests/server-static.test.mjs (6 tests) covering traversal rejection,
  a percent-encoded attempt, and the SPA-fallback case specifically.

- index.html (js/functionality-from-untrusted-source, medium): every
  other CDN script tag already had an integrity="sha512-..."
  attribute; only mermaid.min.js was missing one. Computed the hash
  locally and cross-checked it against cdnjs's own published SRI
  metadata before trusting it -- they matched.

Full suite 132/132 (up from 126). Clean build. tests/server-smoke.mjs
re-verified (17/17). Manually re-confirmed live: traversal still
rejected (400), SPA fallback for an unmatched route still works
(200), a real asset still serves (200).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…deflow-and-establish-the-railway-application

MOO-67: Modularize CodeFlow and establish the Railway application shell
Structured, versioned identity for any point in a specific revision of
a specific repository: repository identity, resolved revision, path,
scope-chain (symbolPath), symbol kind, and optional range. Serializes
canonically (stable key order) and encodes to an opaque base64url
token for routes/cache keys, avoiding fragile delimiter-based string
splitting.
Normalizes repository/branch/commit/PR requests into one canonical
AnalysisContext (owner, repo, mode, resolvedSha, optional base/head
SHA, PR number), rejecting mixed or contradictory revision fields.
assertContextPropagation enforces that a file/function drill-down
request can never silently switch revisions relative to its parent
graph.
One schema (schemaVersion, layer, context, nodes, edges, groups,
analyzer provenance, confidence, warnings, rendering hints) shared by
repository/file/function graphs. Layers keep distinct kind/hint
vocabularies (shape, semantic colorRole, layout preference) while
validation rejects cross-layer node/edge mismatches and dangling edge
references with clear messages, and safely ignores unknown extra
fields anywhere in the tree.
One AdapterResult envelope (graph, warnings, diagnostics, provenance,
timing, cache metadata, partial flag) for every layer adapter, plus a
fixed set of stable error categories (github_access,
unsupported_input, parser_failure, subprocess_failure,
malformed_analyzer_output, timeout, renderer_failure, internal_error).
sanitizeDiagnostic strips stack traces and redacts secret-shaped keys
at any depth, applied unconditionally by buildAdapterResult so a
producer can't forget it. Partial-success results may omit the graph
entirely, but never carry a schema-invalid one.
Shared interaction contract: single click emits selection/focus only
(createSelectionEvent); double click emits drill-down intent
(createDrillDownEvent) carrying the source coordinate and target
layer, but only when isDrillDownEligible — an ambiguous or
under-resolved coordinate throws NavigationError rather than
dispatching an incorrect drill-down. createOpenSourceEvent covers the
separate "view raw source" action. NavigationHistory is a
back/forward breadcrumb stack keyed by graph cache key + selection +
coordinate, truncating the forward branch on push like standard
browser history.

This is the real seam MOO-67 Commit 4E's node-activate no-op was left
for.
buildCacheKey derives a stable sha256-based key from normalized
revision context, analyzer name/version, GraphIR schema version,
requested coordinate, and depth/options — canonicalized (deep
key-sorted) so equivalent normalized requests collapse to the same
key while any real difference (revision, analyzer version, schema
version, depth, requested coordinate) always changes it.
isCacheStale treats a schema-version mismatch as always stale and
supports an optional TTL. buildProvenanceSummary reports visible
provenance plus resolved/unresolved adapter-match counts.
- src/graph-ir/index.js: single barrel import surface for the whole
  contract (no name collisions across the six modules).
- tests/fixtures/graph-ir/{repository,file-pyan,function-codevisualizer}.json:
  representative GraphIR fixtures for all three layers, regenerable
  via scripts/gen-graph-ir-fixtures.mjs, validated in CI.
- examples/minimal-graphir-adapter.mjs: a runnable example adapter
  proving GraphIR can be produced and consumed (selection + drill-down
  events, provenance summary) using only src/graph-ir/index.js, no
  application code.
- docs/graph-ir-contract.md: the repository -> file -> function
  identity flow, module-by-module ownership, and extension rules for
  future languages/analyzers/renderers/LLM annotation.
- docs/baseline.md: MOO-68 completion summary alongside the existing
  MOO-67 regression baseline.

Full suite: 207/207 (132 pre-existing + 75 new across all seven
commits). This closes MOO-68 -- MOO-69 can now adapt the repository
layer against these contracts instead of the MOO-67 neutral seams
directly.
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

@OwenTanzer is attempting to deploy a commit to the braedonsaunders' projects Team on Vercel.

A member of the Team first needs to authorize it.

@OwenTanzer

Copy link
Copy Markdown
Author

Opened against the wrong repo by mistake (upstream fork parent instead of OwenTanzer/codeflow). Correct PR: see OwenTanzer/codeflow.

@OwenTanzer OwenTanzer closed this Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant