Codync
무료 오픈소스 1:1 대안, Grok Bot과 Muse 대신
학생이 주도하거나 자체 운영하는 다수의 Claude Code 세션을 위한 공유 저장소입니다
One shared repository, any number of Claude Code sessions, driven by students or running on their own. OpenSpineConsortium builds the protocol and the tools that keep them from colliding, and runs its
개발 도구HTML · JavaScriptGitHub @OpenSpineConsortium온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기One shared repository, any number of Claude Code sessions, driven by students or running on their own. OpenSpineConsortium builds the protocol and the tools that keep them from colliding, and runs its spine imaging research on them.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗The public website for the OpenSpineConsortium, served by GitHub Pages at https://openspineconsortium.com. Static HTML, CSS and vanilla JS; no framework, no external scripts.
One command regenerates every page:
python -X utf8 tools/build_site.py # build, stamp, check
python -X utf8 tools/build_site.py --external # also ask every outside link for its status
python -X utf8 tools/build_site.py --check-only # write nothing; check what is on disk
python -X utf8 tools/build_site.py --out DIR # copy the site to DIR (new or empty, outside this
# folder) and build and check there instead
It exits non-zero and names the page when a check fails. Commit what it wrote. --out is the
dry run: it touches nothing here, and diff -r against DIR shows what a real build would change.
Every pull request runs the required check "check" (.github/workflows/ci.yml, bash scripts/check.sh): the build's --check-only, the tests and the review record (CONTRIBUTING.md). Master takes changes only by pull request.
python -X utf8 tools/build_site.py rewrites every
page, stamps every stylesheet, script, image, video and document reference with one new
?v=<yyyymmddHHMM> (UTC), and runs its checks. Push the whole tree it wrote in one commit,
so no page on the live site carries a stamp from a different build. The stamp is what busts
the browser and CDN caches for everything a page loads.Cache-Control: max-age=600, so for up to ten minutes after the deploy a visitor (and the
CDN) may still get the previous HTML, which points at the previous, still-present assets.
Nothing to do but wait; a hard reload shows the new page at once./ashley/ is the product's old address, and two tables in tools/build_site.py say
what every file there is. REDIRECTS (old file -> new address): the build writes each
as a two-line page that sends the visitor on with the query and the fragment
(/ashley/request.html?access_id=ab1234 lands on /beethoven/request.html?access_id=ab1234)
and a meta refresh for a browser without scripts. KEPT_FILES: files installed builds
read by address, or the exact texts people accepted, kept byte for byte with their
SHA-256 pinned (services.json, which Ashley for VS Code 3.0.0 and Beethoven 4.0.0 to
6.0.0 read; bridge-killlist.json; the version 4 license.txt, license-full.txt and
privacy.html; bridge/privacy.html and bridge/terms.html). The build never writes a
kept file, fails if one changes, and fails on any file under /ashley/ that is in
neither table. Remove an entry only when no installed build that reads it is left.git show 28d2cee:index.html.tools/pages/ (the convention every page follows)Every page on the site is one body fragment wrapped in one shared shell.
The shell (head, header, section strip, breadcrumb, footer, scripts) is written by
tools/build_site.py from the site map at the top of that script, so no two pages
can disagree about navigation. You never write a <header> or a <footer> by hand.
Where a body lives. Take the page's output file, drop .html, and put
body.html in that folder under tools/pages/:
| URL | output file | body file |
|---|---|---|
/ | index.html | tools/pages/index/body.html |
/beethoven/ | beethoven/index.html | tools/pages/beethoven/index/body.html |
/onboarding/ | onboarding/index.html | tools/pages/onboarding/index/body.html |
/research/conferences/ | research/conferences/index.html | tools/pages/research/conferences/index/body.html |
/beethoven/request.html | beethoven/request.html | tools/pages/beethoven/request/body.html |
What a body file contains. A few lines of front matter between two ---
lines, then the HTML that goes inside <main id="main">:
---
title: Beethoven · OpenSpineConsortium
description: One sentence for search results and link previews.
css: /onboarding/onboarding.css
js: /assets/js/deck.js module
canonical: https://openspineconsortium.com/beethoven/
body_class: page-home
strip: no
breadcrumb: no
---
<section class="section">
…
</section>
<script>/* inline scripts are allowed and kept as written */</script>
| key | required | meaning |
|---|---|---|
title | yes | the <title> |
description | yes | <meta name="description"> and the link preview |
css | no | extra stylesheets, comma separated, root-absolute |
js | no | extra scripts, comma separated, root-absolute; add module after a path for type="module" |
canonical | no | <link rel="canonical"> (default: the page's own address) |
robots | no | <meta name="robots">, e.g. noindex (the admin page) |
body_class | no | classes on <body> |
strip | no | no hides the section strip on this page |
breadcrumb | no | no hides the breadcrumb (the home page has none anyway) |
app | no | yes gives the page the Home Screen app's head lines (the manifest, Apple's touch icon and its app lines) and a viewport that covers the whole screen (viewport-fit=cover), so the page pads its own edges by the safe areas; one page only, the control panel, since the manifest's address and scope are /symphony/control/ (APP_MANIFEST in tools/build_site.py) |
Directives a body may use; the build fills them in:
| directive | becomes |
|---|---|
<!-- build:request-form services=<url> --> | the access-request form and its script (tools/templates/request-form.html); services is where it reads the access service's address (default /beethoven/services.json; request.html passes services.json, relative, as it always has, and the build checks that it still does) |
<!-- build:request-form services=<url> fine=short --> | the same form and the same script without the three plain hints under the fields and without the "What this page sends" paragraph (request.html keeps it). The CV box and the consent line, which links the privacy statement, stay in both (the Google account's line, <small class="why">, left with the field on 2026-10-13). The home page uses it |
<!-- build:browsers --> | the one install card: Chrome, "On the Chrome Web Store, with no invitation needed" (BROWSERS, LISTING_NOTE; the listing is public since Beethoven 20.0.0, 2026-10-13). No button while store_url is None; set it to the listing's https://chromewebstore.google.com/... address only if the card should link it. Nothing is downloaded from this site |
<!-- build:section-index --> | on a tab's landing page (/research/, /data/), a list of the pages one level below its children (the six tutorials, the Spine MRI parts), made from SITE, so every page is at most two clicks from the header |
<!-- build:docs-products -->, <!-- build:docs-pages -->, <!-- build:docs-buttons -->, <!-- build:docs-history -->, <!-- build:docs-live-repos -->, <!-- build:docs-built --> | the Docs tab's generated parts (below, "The Docs tab") |
<!-- build:vscode-install --> | the one install card of Beethoven for VS Code: the Marketplace button once VSCODE_STORE_URL is set, else "listing pending" |
<!-- build:license-title -->, <!-- build:license-toc -->, <!-- build:license-text --> | the browser edition's Trial Edition License Agreement, rendered from its repository's LICENSE.txt |
<!-- build:privacy-title -->, |
Rules for bodies:
/beethoven/terms.html, /assets/img/…).?v=: the build stamps every local stylesheet, script, image,
video and document reference with ?v=<yyyymmddHHMM> (UTC) on every run./assets/css/style.css, /assets/css/site.css and
/assets/js/main.js (the menu, the .reveal animation, the People and Goals
renderers). Anything with class reveal stays invisible until main.js runs.SITE in tools/build_site.py first.SITE at the top of tools/build_site.py is the only place navigation is defined:
eight tabs (Home, Symphony, Beethoven, Onboarding, Research, Data & Tools, People, Docs), the
"Get Involved" button (to /beethoven/request.html), and their children. Home is the
lab page and has no children, so it gets no strip and no footer column; its two figures
are drawn by tools/home_figures.py (below). From it the build writes
aria-current on the tab you
are in; one row down to 721px, a menu under 720px (#navToggle, handled by main.js);main.js, the class nav-hover set in the header block); on a phone or a tablet the
current section's strip sits in the page under the bar;sitemap.xml (canonical addresses only: a page whose canonical is elsewhere is left out) and robots.txt.Adding a page: add a node(...) to SITE, write its body file, run the build.
kind="page" is generated from a body; kind="sub" is a page that keeps its own
HTML (the atlas, the Spine MRI series, the tutorials, Celebrate Life) and receives
the shell between <!-- site:head -->, <!-- site:header --> and
<!-- site:footer --> markers, so a second run replaces rather than adds;
kind="app" (the PACS demo) receives the header only, in the page flow, because
the viewer fills the screen. The sub-sites' content is never touched; the build
removes only their old header, their inline menu script and links to the retired
one-page anchors (../index.html#datasets becomes /data/datasets/).
The tutorials are generated by tutorials/_build.py from Markdown: after running
it, run tools/build_site.py to put the shell back.
/docs/, written by every buildThe Docs tab keeps itself up to date: the build writes it from what it already reads, so a page,
a button or a version that goes live is in the docs in the same build, with nothing to edit by
hand. PRODUCTS and LIVE_REPOS in tools/build_site.py (THE DOCS SECTION) are the only lists
in it, and a new product is one entry there.
/docs/: each product in PRODUCTS, what it is, its version (read from its repository at
build time, or from GitHub in the browser for the protocol and the plugin) and where to start;
then every page in SITE, hidden ones included, with its front matter's description and a tag
for a page that signs in, one reached only by a link and one kept out of search. Since the Docs
tab lists every page, the build fails when a page in SITE is more than two clicks from the
header and footer, hidden or not, and when an .html file is on disk but not in SITE./docs/buttons.html: every <button> and .btn link on every page, read from the page's
body (or, for a page that keeps its own HTML, its file without the shell), and the buttons its
own scripts make through a btn('...') helper, marked "when there is something to act on"./docs/changes.html: the pull requests merged in the last 30 days in the public
repositories of LIVE_REPOS, read by assets/js/docs.js from GitHub's public API with no
token (60 reads an hour from one address, so each answer is kept in the tab for ten minutes),
then each product's own change notes: the "Version N ..." paragraphs of the privacy statement
it ships with, newest first. Private and internal repositories stay out of LIVE_REPOS,
since the site is public.A hidden page (hidden=True) is left out of the menus, the strip, the footer and the sitemap and
still listed on /docs/; a page whose front matter says robots: noindex stays out of the
sitemap whether hidden or not.
tools/home_figures.pyThe lab diagram and the git graph on / are SVG drawn from coordinates in that script, each in
a wide layout and a phone layout, because their labels are SVG text and scale with the drawing.
python -X utf8 tools/home_figures.py rewrites them between <!-- lab:start --> ...
<!-- lab:end --> and <!-- graph:start --> ... <!-- graph:end --> in
tools/pages/index/body.html, and the keyframes whose timing comes from the coordinates
between the two generated by tools/home_figures.py comments in assets/css/lab.css. Change
the drawing there and run it, then the build; the prose around the figures and the rest of
lab.css are written by hand.
tools/app_icons.pyThe control panel installs from a phone's browser as an app of its own (the page's app: yes), and
a Home Screen wants raster icons. python -X utf8 tools/app_icons.py draws the four PNGs under
assets/img/ (symphony-app-180.png, Apple's touch icon; symphony-app-192.png and
symphony-app-512.png, the manifest's; symphony-app-maskable-512.png, the one Android may cut to
a shape) from Symphony's mark, with the standard library alone. It rewrites a file only when its
decoded pixels differ from the drawing, since another zlib (zlib-ng, behind Python 3.14 on Windows)
packs the same pixels into other bytes. The build writes the manifest itself,
symphony/control/manifest.webmanifest, from APP_MANIFEST, and its check (check_app) fails when
the manifest on disk is not that or an icon is missing or not the size it says.
| old anchor | page |
|---|---|
#home (hero), #project | /research/ |
#datasets | /data/datasets/ (plus "Use the datasets" from #join) |
#people | /people/ |
#organization | /people/organization/ |
#education | /people/education/ |
#workshop | /research/workshop/ (the tutorials hang under it) |
#goals | /research/goals/ |
#conferences | /research/conferences/ |
#gallery (the 3-D specimens) | /data/gallery/ |
#hardware | /data/hardware/ |
#outside | /research/outside/ |
#join | /onboarding/ ("Other ways to get involved") |
The last committed one-page site is git show 28d2cee:index.html.
/data/ is a landing page of tiles for its children, and /atlas/ is listed under
Data & Tools.
Beethoven has two editions, and both live under /beethoven/:
/ and /beethoven/ are its product page (its homepage
is /beethoven/), with its agreement, privacy statement, support, request and
reviewers pages beside it. The texts come from its repository (../beethoven/, or
../ashley-bridge/ until the folder is renamed; BEETHOVEN_REPO overrides), and its
version from that repository's extension/package.json. It is for Chrome only and is
distributed only through its Chrome Web Store listing, public since 20.0.0
(2026-10-13; until 19.0.0 a private one whose testers were the lab's Google Group, the
request form asking for each student's Google account). The site offers no package:
beethoven/downloads/ is gone, and the build fails if that folder, a package file
(beethoven-*.zip, .crx, .xpi) or a link to one comes back, or if a page names
another browser (the names are OTHER_BROWSERS in the script; a text rendered from the
browser repository that names one is reported as a note, because the change belongs in
that file).
The 7.0.0 packages are still in this repository's git history (commit 816e9c4);
removing them from history would be a rewrite and is the maintainer's decision.tools/templates/request-form.html) posts v: 2 to the access
service's /request: access_id, name, email (the AccessID at wayne.edu),
google_account, purpose (required, 500 characters at most), product: 'bridge'
and, if one was added, cv: {name, type, base64}: a PDF or Word file of at most 5 MB
(5 × 1024 × 1024 bytes, checked in the page before sending), dropped on the box or
chosen with the file dialog, whose type is taken from its first bytes, never from the
browser's guess. What the page says after sending depends on the answer: 202 with
mailbox: 'confirm' means the service first e-mailed a confirmation link to the
student's Wayne State address ("Sent. Check your Wayne State mail for a confirmation
link."); any other 202 means the lab lead hears at once ("Sent. You hear from the lab
lead."). The service must be deployed with v: 2 before the site, because the form no
longer sends the old body./beethoven/vscode/ (the "For VS Code" pill): its product
page, terms.html (Trial Edition), terms-full.html (Full Edition), privacy.html
and third-party.html, rendered from its repository's texts
(, or until the folder is renamed;
overrides), plus and
, byte-for-byte copies of the two agreements. Its version is
read from its . The other spellings
and are redirects in .One approval covers both editions; /beethoven/request.html is the one request form.
Every text is checked word for word against its source, and the old product name may
appear on a page only inside a person's name or one of the KEPT identifiers (the key
prefixes ASHLEY1./ASHLEYREV1, the folders ~/.ashley, ~/.ashley-local and
/shared/ashley, the old settings ashley.*, the old Marketplace id and service name),
which stay because renaming them would break an installed copy. VSCODE_STORE_URL,
BROWSERS (Chrome only; store_url the listing's address, or None) and LISTING_NOTE are at the top of
tools/build_site.py.
Retired: the browser repository's tools/site_beethoven_build.py and the VS Code
repository's tools/site_build.py and docs/site/ no longer build pages for this site;
everything is generated here.
Never edited by hand: beethoven/services.json and beethoven/killlist.json (both
editions fetch them; the build checks that services.json names every route both read,
on beethoven-access, and never writes either), the KEPT_FILES under /ashley/, and
googlec3911e7fb095977e.html.
GitHub Pages serves HTML with max-age=600, so a visitor can see the previous HTML
for up to ten minutes after a deploy (see Publishing above). Everything the HTML loads carries a new
?v=<yyyymmddHHMM> from every build, so the new HTML never pairs with an old
stylesheet or script. ES-module imports inside scripts (gallery.js → viewer.js →
three.js; xr.js → infer.js, remote.js) carry content hashes instead, which the
build recomputes (MODULE_EDGES); a stamp on the entry script alone would not make
a browser refetch what it imports. tools/stamp_assets.py did this for the one-page
site and is superseded by the build. Data files (contributions/manifest.json,
meded/survey-summary.json, beethoven/services.json) are fetched with
revalidation and need no stamp.
The People grid (/people/) and the CNS 2026 list (/research/goals/) render at
load time from contributions/manifest.json; the Education Outcomes chart
(/people/education/) from meded/survey-summary.json. Edit those files, not the
markup. To add a person, append to people (copy an existing entry); headshots go in
headshots/ as lastname,firstname[,mi].ext (see headshots/README.md); people
without a photo get an initials avatar. Bump HEADSHOT_V in assets/js/main.js when
a portrait is replaced. Never commit the raw survey CSV: it holds names and
demographics and is excluded by .gitignore; meded/README.md says how to regenerate
the summary.
python -m http.server 8000 # from the repository root
# then open http://localhost:8000
Paths are root-absolute, so open the site through the server, not as file://.
The request form cannot send from localhost (the access service answers only
https://openspineconsortium.com); test a submission on the live site.
GitHub Pages, branch master, folder /, custom domain in CNAME. Tick Enforce
HTTPS in Settings → Pages: until then http://openspineconsortium.com/ is served
without a redirect, and the access service refuses a request from an http://
page. Every generated page upgrades itself to https as a fallback.
Site content © OpenSpineConsortium. Datasets referenced here are redistributed under the terms of their original source licenses; each dataset card says where the data comes from and how it is licensed.
<!-- build:privacy-text -->the browser edition's privacy statement, rendered from its PRIVACY.md with pandoc |
<!-- build:vscode-license-… -->, <!-- build:vscode-full-… --> (-title, -toc, -text) | Beethoven for VS Code's Trial and Full Edition License Agreements, from its repository's extension/LICENSE.txt and LICENSE-FULL.txt |
<!-- build:vscode-privacy-… -->, <!-- build:vscode-third-party-… --> (-title, -text) | its extension/PRIVACY.md and extension/THIRD_PARTY_NOTICES.md, with pandoc |
<!-- build:symphony-privacy-… --> (-title, -text) | Symphony for Chrome's privacy statement, from its repository's PRIVACY.md, with pandoc |
{{version}}, {{vscode_version}}, {{license_version}}, {{privacy_version}}, {{vscode_license_version}}, {{vscode_full_version}}, {{vscode_privacy_version}}, {{symphony_privacy_version}} | the two editions' versions and the texts' version numbers |
extension/../beethoven-vscode/../ashley/BEETHOVEN_VSCODE_REPObeethoven/vscode-license.txtvscode-license-full.txtextension/package.jsonbeethoven/vscode-privacy.htmlbeethoven/third_party_notices.htmlREDIRECTS/symphony/ (its own tab): the GitHub App that sets a team of Claude Code
teammates up on a repository, and Symphony for Chrome, the extension that puts its install
one click away on github.com. The page is in the Beethoven home's style with the promo film
(symphony/symphony-promo.webm, .mp4, poster.jpg) in the diagram's place; the
extension's privacy statement, /symphony/privacy.html (the address its Chrome Web Store
listing names), is rendered from its repository's PRIVACY.md (../symphony-extension/;
SYMPHONY_REPO overrides) and checked word for word, like the Beethoven texts./symphony/control/ (in the Symphony strip and the footer
beside the Tokens page, and kept out of search by its noindex): drives a Symphony repository through GitHub's REST API as the signed-in
person, from the browser. "Sign in with GitHub" is the way in, SymphonyBoard's user token
through the stateless sign-in service in symphony-app, pressable once SIGNIN_SERVICE and
CLIENT_ID at the top of assets/js/symphony-panel.js name the deployed service (until then
the button shows unpressable, with a line saying why); a fine-grained token kept in the browser
is the fallback, folded under "Use a fine-grained token instead". It shows the board (STATE.md
on the branch board, rendered by a small Markdown renderer of its own), the open issues by the
protocol's labels, the worker registry, the open pull requests with their checks and the Actions
runs; it queues a task, gives an issue to a worker, opens the "Add a worker" issue, asks Claude
(an issue or a comment mentioning @claude), asks the assistant (a question as a comment on an
issue "Assistant: " labelled assistant, opened once per repository and person with
write access, whose read-only answer from the Claude workflow the page polls for with an ETag
and renders, acting only on the few lines of its symphony block: focus, open, filter, show),
draws each open epic's tasks and what blocks them (its Dependencies tab: GitHub's sub-issues
and blocked-by relations, read as the person, built to the same Mermaid text the protocol's board
writes, byte for byte, % and $ written as #37; and #36; by both so that no title can open
a Mermaid directive or its math, and drawn by the vendored Mermaid 12.2.0,
assets/js/vendor/mermaid.min.js, MIT, loaded only when there is a chart to draw, with a table
under each chart; the Board tab draws the charts the board's own text holds the same way, only
where every line of a block is one the board writes, and shows any other block as text; it
writes nothing),
starts the workers, the board, the checks and the
metrics by hand, merges a green pull request (the token needs Contents: Read and write for that), and gives the two presses that set Symphony up on
a repository named without the topic symphony. Every state is written in words and every action
is a named button, so a browsing Claude can drive it; ?repo=OWNER/NAME lands on one
repository. /symphony/cost.html beside it reads METRICS.json on the branch metrics of
the chosen repository and prices the same tokens under the other ways of running coding agents
on GitHub, with a calculator; every price names its source. The console's Folders tab,
/symphony/control/#folders, makes the repository the bar has chosen track files and
folders of the person's OneDrive: one press connects the drive through Microsoft's consent,
which comes back to the console's own address as GitHub's sign-in does and is told from it by
its state, and the sign-in service's /drive routes; the tab draws the drive's folders from
Microsoft Graph to tick from (its own list, in place of OneDrive's File Picker, which needs a
SharePoint-resource token, an iframe protocol and a tenant-specific address), GitHub's own
template form makes the repository and "Find the repository I just made" chooses it in the
bar, and Track seals the drive's refresh token as the secret SYMPHONY_DRIVE_REFRESH, writes
.github/symphony/drive.json and starts the first sync; Google Drive waits for a way that
needs no key. /symphony/door/, the reader, opens one repository the signed-in person can
read, named in the address as ?repo=OWNER/NAME (with none, the one this browser last opened
there; ?door=LABEL opens a starting point of the repository's door.json, as the home page's
Read the Textbook button does), live through GitHub's API with their own
token: its folders, its Markdown rendered, its pictures, its text files and its HTML pages sealed
in a sandboxed frame (assets/js/door.js, assets/css/door.css). It is hidden and noindex
like /beethoven/admin.html below, since its address is sent with the repository in it, and
tests/test_door_browser.py drives it in Chromium where Playwright is installed. The three pages
share the script and assets/css/control.css, which ride on the admin page's .admin styles./beethoven/admin.html, the maintainer's approval page, is generated like any page
but hidden: no navigation leads to it, it carries noindex and it is not in the
sitemap. It reads the access service's address from beethoven/services.json
(beethoven-access).