Spaces:
Running
Running
Launch One Good Commit static Space
Browse files- LICENSE +21 -0
- README.md +106 -4
- index.html +57 -17
- static-app.js +205 -0
- static-styles.css +1 -0
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Joshua Bloodworth
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,10 +1,112 @@
|
|
| 1 |
---
|
| 2 |
title: One Good Commit
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
|
|
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: One Good Commit
|
| 3 |
+
emoji: 🃏
|
| 4 |
+
colorFrom: yellow
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
short_description: Evidence-backed model card contribution assistant
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# One Good Commit
|
| 14 |
+
|
| 15 |
+
One Good Commit reads a public Hugging Face model card and prepares a bounded
|
| 16 |
+
documentation contribution packet for a human to review.
|
| 17 |
+
|
| 18 |
+
It answers three narrow questions:
|
| 19 |
+
|
| 20 |
+
1. What relevant documentation can be located in the public model card?
|
| 21 |
+
2. Which selected documentation signals were not found by the deterministic review?
|
| 22 |
+
3. What questions could a prospective contributor ask without inventing facts?
|
| 23 |
+
|
| 24 |
+
The tool does **not** score compliance, certify safety, evaluate model quality,
|
| 25 |
+
run the model, or post a Discussion or pull request.
|
| 26 |
+
|
| 27 |
+
## Why this exists
|
| 28 |
+
|
| 29 |
+
Model cards help people discover, reproduce, understand, and responsibly use
|
| 30 |
+
models. Maintainers also have uneven time, information, and documentation needs.
|
| 31 |
+
This project is a small contribution aid: it keeps evidence attached to each
|
| 32 |
+
observation and leaves every consequential decision with a person.
|
| 33 |
+
|
| 34 |
+
## Method
|
| 35 |
+
|
| 36 |
+
The app retrieves two public sources:
|
| 37 |
+
|
| 38 |
+
- the model repository's Hub API record;
|
| 39 |
+
- the selected revision of its `README.md`.
|
| 40 |
+
|
| 41 |
+
It then runs deterministic checks for documentation signals related to:
|
| 42 |
+
|
| 43 |
+
- license;
|
| 44 |
+
- language coverage;
|
| 45 |
+
- task and model type;
|
| 46 |
+
- base model or provenance;
|
| 47 |
+
- intended uses;
|
| 48 |
+
- out-of-scope uses;
|
| 49 |
+
- limitations, risks, or biases;
|
| 50 |
+
- training data;
|
| 51 |
+
- evaluation evidence;
|
| 52 |
+
- training and reproducibility details;
|
| 53 |
+
- citations or supporting papers.
|
| 54 |
+
|
| 55 |
+
A result can be:
|
| 56 |
+
|
| 57 |
+
- **Documented:** relevant metadata or card text was located.
|
| 58 |
+
- **Review:** related text was located, but a structured or clearer source may
|
| 59 |
+
still need human review.
|
| 60 |
+
- **Not found:** the review did not locate relevant information.
|
| 61 |
+
|
| 62 |
+
“Not found” is deliberately limited language. It does not mean the information
|
| 63 |
+
does not exist, the section is mandatory, or the maintainer has done something
|
| 64 |
+
wrong.
|
| 65 |
+
|
| 66 |
+
## Deployment
|
| 67 |
+
|
| 68 |
+
The public Space is intentionally static so it can run without a paid server,
|
| 69 |
+
GPU, account system, or secret. Its browser-side code makes read-only requests
|
| 70 |
+
to the selected public model's Hub API record and `README.md`; model-card text
|
| 71 |
+
is treated as untrusted and rendered as text, not HTML.
|
| 72 |
+
|
| 73 |
+
The earlier Python/Gradio prototype and fixture tests remain in this repository
|
| 74 |
+
as a local reference implementation. The deployed Space is `index.html`,
|
| 75 |
+
`static-app.js`, and `static-styles.css`.
|
| 76 |
+
|
| 77 |
+
## Run the local reference implementation
|
| 78 |
+
|
| 79 |
+
```bash
|
| 80 |
+
python -m venv .venv
|
| 81 |
+
source .venv/bin/activate
|
| 82 |
+
pip install -r requirements.txt
|
| 83 |
+
python app.py
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
The analyzer and its tests use only the Python standard library:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
python -m unittest discover -s tests -v
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Contribution boundary
|
| 93 |
+
|
| 94 |
+
Before contacting a maintainer:
|
| 95 |
+
|
| 96 |
+
1. Read the complete card and linked primary sources.
|
| 97 |
+
2. Confirm that the suggested section applies to that model.
|
| 98 |
+
3. Remove speculative placeholders.
|
| 99 |
+
4. Ask questions where only maintainers can know the answer.
|
| 100 |
+
5. Prefer one small, accurate improvement over a broad rewrite.
|
| 101 |
+
|
| 102 |
+
No output from this tool should be posted automatically.
|
| 103 |
+
|
| 104 |
+
## Privacy and access
|
| 105 |
+
|
| 106 |
+
The current version accepts public, ungated model repositories only. It does not
|
| 107 |
+
accept uploaded documents or store model card content in an application
|
| 108 |
+
database.
|
| 109 |
+
|
| 110 |
+
## License
|
| 111 |
+
|
| 112 |
+
MIT
|
index.html
CHANGED
|
@@ -1,19 +1,59 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
| 6 |
+
<meta name="description" content="An evidence-backed, human-reviewed contribution assistant for public Hugging Face model cards.">
|
| 7 |
+
<title>One Good Commit</title>
|
| 8 |
+
<link rel="stylesheet" href="static-styles.css">
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<main class="shell">
|
| 12 |
+
<header class="hero">
|
| 13 |
+
<p class="eyebrow">A HUMAN-REVIEWED CONTRIBUTION ASSISTANT</p>
|
| 14 |
+
<h1>One Good Commit</h1>
|
| 15 |
+
<p class="lede">Read a public Hugging Face model card, trace what it documents, and prepare a careful contribution packet for a human to review.</p>
|
| 16 |
+
<div class="principles" aria-label="Project principles">
|
| 17 |
+
<span>No compliance score</span><span>No invented details</span>
|
| 18 |
+
<span>No automatic posts</span><span>Evidence stays attached</span>
|
| 19 |
+
</div>
|
| 20 |
+
</header>
|
| 21 |
+
|
| 22 |
+
<section class="input-card" aria-label="Review a public model card">
|
| 23 |
+
<form id="review-form">
|
| 24 |
+
<label for="repo-id">Public model ID</label>
|
| 25 |
+
<div class="input-row">
|
| 26 |
+
<input id="repo-id" name="repo-id" autocomplete="off" placeholder="owner/model-name" required>
|
| 27 |
+
<button type="submit">Review card</button>
|
| 28 |
+
</div>
|
| 29 |
+
</form>
|
| 30 |
+
<p class="example-label">Try an established public card</p>
|
| 31 |
+
<div class="examples" aria-label="Example model cards">
|
| 32 |
+
<button type="button" data-example="sentence-transformers/all-MiniLM-L6-v2">sentence-transformers/all-MiniLM-L6-v2</button>
|
| 33 |
+
<button type="button" data-example="google-bert/bert-base-uncased">google-bert/bert-base-uncased</button>
|
| 34 |
+
<button type="button" data-example="openai-community/gpt2">openai-community/gpt2</button>
|
| 35 |
+
</div>
|
| 36 |
+
<p class="input-note">Public repositories only. This app reads the selected README and Hub metadata; it does not run or evaluate the model.</p>
|
| 37 |
+
</section>
|
| 38 |
+
|
| 39 |
+
<section id="notice" class="notice" hidden aria-live="polite"></section>
|
| 40 |
+
<section id="results" hidden aria-live="polite">
|
| 41 |
+
<div id="summary"></div>
|
| 42 |
+
<div class="tabs" role="tablist" aria-label="Review output">
|
| 43 |
+
<button role="tab" aria-selected="true" aria-controls="evidence-panel" id="evidence-tab" data-tab="evidence-panel">Evidence</button>
|
| 44 |
+
<button role="tab" aria-selected="false" aria-controls="worksheet-panel" id="worksheet-tab" data-tab="worksheet-panel">Contribution worksheet</button>
|
| 45 |
+
<button role="tab" aria-selected="false" aria-controls="discussion-panel" id="discussion-tab" data-tab="discussion-panel">Discussion draft</button>
|
| 46 |
+
</div>
|
| 47 |
+
<section id="evidence-panel" role="tabpanel" aria-labelledby="evidence-tab" class="tab-panel"></section>
|
| 48 |
+
<section id="worksheet-panel" role="tabpanel" aria-labelledby="worksheet-tab" class="tab-panel" hidden></section>
|
| 49 |
+
<section id="discussion-panel" role="tabpanel" aria-labelledby="discussion-tab" class="tab-panel" hidden></section>
|
| 50 |
+
<button id="download-review" class="download" type="button">Download complete review</button>
|
| 51 |
+
</section>
|
| 52 |
+
|
| 53 |
+
<footer>
|
| 54 |
+
<strong>Method boundary.</strong> This tool checks whether selected documentation signals can be located. It does not establish truth, safety, quality, legal status, or compliance. Every contribution remains a human decision.
|
| 55 |
+
</footer>
|
| 56 |
+
</main>
|
| 57 |
+
<script src="static-app.js"></script>
|
| 58 |
+
</body>
|
| 59 |
</html>
|
static-app.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
"use strict";
|
| 3 |
+
|
| 4 |
+
const MAX_README_BYTES = 2_000_000;
|
| 5 |
+
const REPO_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
| 6 |
+
const checks = [
|
| 7 |
+
["license", "License", ["license", "license_name", "license_link"], [], [], "Confirm the license identifier or link that governs reuse."],
|
| 8 |
+
["language", "Language coverage", ["language", "languages"], ["\\blanguages?\\b"], [], "Document the languages represented and any known coverage limits."],
|
| 9 |
+
["task", "Task and model type", ["pipeline_tag", "library_name", "tags"], ["\\bmodel description\\b", "\\btask\\b"], [], "Clarify the task, model family, and expected input/output shape."],
|
| 10 |
+
["base_model", "Base model or provenance", ["base_model"], ["\\bmodel description\\b", "\\bmodel details\\b", "\\bprovenance\\b"], ["\\bbase model\\b", "\\bfine[- ]?tuned from\\b", "\\bderived from\\b"], "Name the base model or explain the model's provenance."],
|
| 11 |
+
["intended_uses", "Intended uses", [], ["\\bintended uses?\\b", "\\buse cases?\\b", "\\buses?\\b", "\\bhow to use\\b"], ["\\bintended for\\b", "\\bcan be used\\b", "\\buse cases?\\b"], "Describe supported use cases and the users or settings considered."],
|
| 12 |
+
["out_of_scope", "Out-of-scope uses", [], ["\\bout[- ]of[- ]scope\\b", "\\bmisuse\\b", "\\bnot intended\\b", "\\bprohibited uses?\\b"], ["\\bshould not\\b", "\\bnot intended\\b", "\\bout[- ]of[- ]scope\\b"], "Identify unsupported or inappropriate uses, where relevant."],
|
| 13 |
+
["limitations", "Limitations, risks, or biases", [], ["\\blimitations?\\b", "\\brisks?\\b", "\\bbias(?:es)?\\b", "\\bethical considerations?\\b"], ["\\blimitations?\\b", "\\brisks?\\b", "\\bbias(?:es|ed)?\\b"], "Describe known limitations, biases, or risks and how they were observed."],
|
| 14 |
+
["training_data", "Training data", ["datasets", "dataset"], ["\\btraining data\\b", "\\bdatasets?\\b", "\\btraining details\\b"], ["\\btrained on\\b", "\\btraining dataset\\b"], "Name or characterize the training data and include source links when possible."],
|
| 15 |
+
["evaluation", "Evaluation evidence", ["model-index", "eval_results", "metrics"], ["\\bevaluation\\b", "\\bresults?\\b", "\\bbenchmarks?\\b", "\\bmetrics?\\b"], ["\\bbenchmark\\b", "\\baccuracy\\b", "\\bf1\\b", "\\bbleu\\b", "\\brouge\\b"], "Link evaluation datasets, metrics, values, and their source or procedure."],
|
| 16 |
+
["reproducibility", "Training and reproducibility details", ["training_args"], ["\\btraining procedure\\b", "\\btraining details\\b", "\\bhyperparameters?\\b", "\\breproducibility\\b"], ["\\blearning rate\\b", "\\bbatch size\\b", "\\btraining steps?\\b"], "Add the training procedure, key parameters, code, or experiment links."],
|
| 17 |
+
["citation", "Citation or supporting paper", ["citation"], ["\\bcitation\\b", "\\breferences?\\b", "\\bpaper\\b"], [], "Add a citation, paper, technical report, or supporting reference if one exists."]
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
const form = document.querySelector("#review-form");
|
| 21 |
+
const input = document.querySelector("#repo-id");
|
| 22 |
+
const notice = document.querySelector("#notice");
|
| 23 |
+
const results = document.querySelector("#results");
|
| 24 |
+
const summary = document.querySelector("#summary");
|
| 25 |
+
const evidencePanel = document.querySelector("#evidence-panel");
|
| 26 |
+
const worksheetPanel = document.querySelector("#worksheet-panel");
|
| 27 |
+
const discussionPanel = document.querySelector("#discussion-panel");
|
| 28 |
+
const downloadButton = document.querySelector("#download-review");
|
| 29 |
+
let latestReview = null;
|
| 30 |
+
|
| 31 |
+
function cleanRepoId(value) {
|
| 32 |
+
let id = value.trim();
|
| 33 |
+
if (id.startsWith("https://huggingface.co/")) id = id.slice("https://huggingface.co/".length).split("/").slice(0, 2).join("/");
|
| 34 |
+
if (!REPO_ID.test(id)) throw new Error("Enter a public model ID in the form owner/model-name.");
|
| 35 |
+
return id;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
function meaningful(value) {
|
| 39 |
+
if (value === null || value === undefined || value === false) return false;
|
| 40 |
+
if (typeof value === "string") return Boolean(value.trim());
|
| 41 |
+
if (Array.isArray(value)) return value.length > 0;
|
| 42 |
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
| 43 |
+
return true;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
function compact(value, limit = 320) {
|
| 47 |
+
const text = (typeof value === "object" ? JSON.stringify(value) : String(value)).replace(/\s+/g, " ").trim();
|
| 48 |
+
return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function frontmatter(readme) {
|
| 52 |
+
const lines = readme.split("\n");
|
| 53 |
+
if (lines[0]?.trim() !== "---") return {};
|
| 54 |
+
const end = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
|
| 55 |
+
if (end < 0) return {};
|
| 56 |
+
const metadata = {};
|
| 57 |
+
let current = null;
|
| 58 |
+
for (const line of lines.slice(1, end)) {
|
| 59 |
+
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
| 60 |
+
const list = line.match(/^\s*-\s+(.+?)\s*$/);
|
| 61 |
+
if (list && current) { if (Array.isArray(metadata[current])) metadata[current].push(list[1].replace(/^['"]|['"]$/g, "")); continue; }
|
| 62 |
+
const key = line.match(/^([A-Za-z0-9_-]+):(?:\s*(.*))?$/);
|
| 63 |
+
if (!key) continue;
|
| 64 |
+
current = key[1];
|
| 65 |
+
const value = (key[2] || "").trim();
|
| 66 |
+
metadata[current] = value ? value.replace(/^['"]|['"]$/g, "") : [];
|
| 67 |
+
}
|
| 68 |
+
return metadata;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
function sections(readme) {
|
| 72 |
+
const lines = readme.split("\n");
|
| 73 |
+
const headings = [];
|
| 74 |
+
let fenced = false;
|
| 75 |
+
lines.forEach((line, index) => {
|
| 76 |
+
if (/^\s*(```|~~~)/.test(line)) { fenced = !fenced; return; }
|
| 77 |
+
if (fenced) return;
|
| 78 |
+
const match = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
|
| 79 |
+
if (match) headings.push({ start: index + 1, level: match[1].length, title: match[2].trim() });
|
| 80 |
+
});
|
| 81 |
+
return headings.map((heading, index) => {
|
| 82 |
+
let end = lines.length;
|
| 83 |
+
for (const next of headings.slice(index + 1)) { if (next.level <= heading.level) { end = next.start - 1; break; } }
|
| 84 |
+
return { ...heading, body: lines.slice(heading.start, end).join("\n").trim() };
|
| 85 |
+
});
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
function sourceLink(repo, revision, line) {
|
| 89 |
+
const link = `https://huggingface.co/${repo}/blob/${revision}/README.md`;
|
| 90 |
+
return line ? `${link}#L${line}` : link;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
function analyze(repo, api, readme) {
|
| 94 |
+
const revision = api.sha || "main";
|
| 95 |
+
const metadata = { ...frontmatter(readme), ...(api.cardData || {}) };
|
| 96 |
+
if (!metadata.pipeline_tag && api.pipeline_tag) metadata.pipeline_tag = api.pipeline_tag;
|
| 97 |
+
if (!metadata.library_name && api.library_name) metadata.library_name = api.library_name;
|
| 98 |
+
if (!metadata.tags && api.tags) metadata.tags = api.tags;
|
| 99 |
+
const parsedSections = sections(readme);
|
| 100 |
+
const lines = readme.split("\n");
|
| 101 |
+
const findings = checks.map(([key, label, keys, headings, keywords, prompt]) => {
|
| 102 |
+
const evidence = [];
|
| 103 |
+
const foundKeys = keys.filter((item) => meaningful(metadata[item]));
|
| 104 |
+
foundKeys.slice(0, 2).forEach((item) => evidence.push({ type: "Model card metadata", text: `${item}: ${compact(metadata[item])}`, url: sourceLink(repo, revision) }));
|
| 105 |
+
const matchedSections = parsedSections.filter((section) => headings.some((pattern) => new RegExp(pattern, "i").test(section.title)));
|
| 106 |
+
if (matchedSections[0]) {
|
| 107 |
+
const item = matchedSections[0];
|
| 108 |
+
evidence.push({ type: "Model card section", text: compact(`${item.title}: ${item.body}`, 420), url: sourceLink(repo, revision, item.start) });
|
| 109 |
+
}
|
| 110 |
+
if (!evidence.length && keywords.length) {
|
| 111 |
+
const foundLine = lines.findIndex((line) => line.trim() && !line.trim().startsWith("#") && keywords.some((pattern) => new RegExp(pattern, "i").test(line)));
|
| 112 |
+
if (foundLine >= 0) evidence.push({ type: "Model card text", text: compact(lines[foundLine], 420), url: sourceLink(repo, revision, foundLine + 1) });
|
| 113 |
+
}
|
| 114 |
+
let status = evidence.length ? "documented" : "not_found";
|
| 115 |
+
let text = evidence.length ? "Relevant information was found in the public card." : "Relevant information was not found by this review.";
|
| 116 |
+
if (key === "evaluation" && matchedSections.length && !foundKeys.length) { status = "review"; text = "Evaluation text was found, but structured evaluation metadata was not found."; }
|
| 117 |
+
if (key === "task" && foundKeys.length === 1 && foundKeys[0] === "tags") { status = "review"; text = "Relevant tags were found; the narrative description may still need review."; }
|
| 118 |
+
return { key, label, status, text, evidence, prompt };
|
| 119 |
+
});
|
| 120 |
+
return { repo, revision, sourceUrl: sourceLink(repo, revision), findings, analyzedAt: new Date().toISOString() };
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
function counts(review) { return review.findings.reduce((all, finding) => ({ ...all, [finding.status]: (all[finding.status] || 0) + 1 }), { documented: 0, review: 0, not_found: 0 }); }
|
| 124 |
+
function statusLabel(status) { return status === "not_found" ? "Not found" : status[0].toUpperCase() + status.slice(1); }
|
| 125 |
+
function make(tag, className, text) { const element = document.createElement(tag); if (className) element.className = className; if (text !== undefined) element.textContent = text; return element; }
|
| 126 |
+
|
| 127 |
+
function renderSummary(review) {
|
| 128 |
+
const count = counts(review);
|
| 129 |
+
summary.replaceChildren();
|
| 130 |
+
const header = make("section", "result-header");
|
| 131 |
+
const heading = make("div");
|
| 132 |
+
heading.append(make("p", "eyebrow", "PUBLIC MODEL CARD REVIEW"), make("h2", "", review.repo));
|
| 133 |
+
const link = make("a", "", "Open reviewed revision ↗"); link.href = review.sourceUrl; link.target = "_blank"; link.rel = "noopener"; heading.append(link);
|
| 134 |
+
const strip = make("div", "count-strip");
|
| 135 |
+
[[count.documented, "documented"], [count.review, "review"], [count.not_found, "not found"]].forEach(([number, label]) => { const entry = make("div"); entry.append(make("strong", "", number), make("span", "", label)); strip.append(entry); });
|
| 136 |
+
header.append(heading, strip);
|
| 137 |
+
const note = make("p", "scope-note", "A narrow result: “Not found” means the review did not locate relevant information. It is not a compliance grade or a claim of maintainer error.");
|
| 138 |
+
const grid = make("section", "finding-grid");
|
| 139 |
+
review.findings.forEach((finding) => { const card = make("article", `finding-card ${finding.status.replace("_", "-")}`); const top = make("div", "finding-topline"); top.append(make("h3", "", finding.label), make("span", "status-pill", statusLabel(finding.status))); card.append(top, make("p", "", finding.text), make("small", "", finding.evidence.length ? `${finding.evidence.length} source item${finding.evidence.length === 1 ? "" : "s"}` : "Maintainer input needed")); grid.append(card); });
|
| 140 |
+
summary.append(header, note, grid);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
function renderEvidence(review) {
|
| 144 |
+
evidencePanel.replaceChildren();
|
| 145 |
+
review.findings.forEach((finding) => { const item = make("article", "evidence-item"); item.append(make("h3", "", `${finding.label} — ${statusLabel(finding.status)}`), make("p", "", finding.text)); if (finding.evidence.length) { finding.evidence.forEach((evidence) => { const link = make("a", "", evidence.type); link.href = evidence.url; link.target = "_blank"; link.rel = "noopener"; const excerpt = make("div", "excerpt", evidence.text); item.append(link, excerpt); }); } else { item.append(make("p", "", `Question for the maintainer: ${finding.prompt}`)); } evidencePanel.append(item); });
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
function worksheet(review) {
|
| 149 |
+
const unresolved = review.findings.filter((finding) => finding.status !== "documented");
|
| 150 |
+
const lines = [`# Suggested model card additions for ${review.repo}`, "", "> Review worksheet—not a drop-in patch. Replace every TODO with maintainer-confirmed information.", ""];
|
| 151 |
+
if (!unresolved.length) return lines.concat(["The selected checks found relevant documentation for every reviewed area.", "A human should still verify accuracy, currency, and applicability."]).join("\n");
|
| 152 |
+
unresolved.forEach((finding) => lines.push(`## ${finding.label}`, "", `<!-- TODO: ${finding.prompt} -->`, ""));
|
| 153 |
+
return lines.concat(["## Review notes", "", "- Confirm that every proposed addition is accurate with the maintainer.", "- Link primary datasets, evaluations, papers, and code where available.", "- Remove sections that are not applicable instead of filling them speculatively."]).join("\n");
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
function discussion(review) {
|
| 157 |
+
const unresolved = review.findings.filter((finding) => finding.status !== "documented");
|
| 158 |
+
const labels = unresolved.slice(0, 4).map((finding) => finding.label.toLowerCase()).join(", ");
|
| 159 |
+
const observation = unresolved.length ? `While reading the public card, I could not confidently locate documentation for ${labels}${unresolved.length > 4 ? `, and ${unresolved.length - 4} other area(s)` : ""}. These may be in another source or may not apply to this model.` : "The review found relevant documentation for each checked area. I would still appreciate confirmation that the information is current.";
|
| 160 |
+
return ["## Possible documentation contribution", "", `Hi—thank you for sharing \`${review.repo}\`.`, "", observation, "", "Would a small documentation contribution be useful? I would be glad to prepare one, but I do not want to infer training, evaluation, licensing, or intended-use details that only the maintainers can confirm.", "", `Card reviewed: ${review.sourceUrl}`, "", "_Drafted for human review. This message has not been posted._"].join("\n");
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
function fullReview(review, worksheetText, discussionText) {
|
| 164 |
+
const count = counts(review);
|
| 165 |
+
const lines = [`# One Good Commit review: ${review.repo}`, "", `- Source: ${review.sourceUrl}`, `- Revision: \`${review.revision}\``, `- Analyzed: ${review.analyzedAt}`, `- Documented: ${count.documented}`, `- Review: ${count.review}`, `- Not found: ${count.not_found}`, "", "> “Not found” means this deterministic review did not locate relevant information. It is not a claim of noncompliance, inaccuracy, or maintainer error.", "", "## Evidence", ""];
|
| 166 |
+
review.findings.forEach((finding) => { lines.push(`### ${finding.label} — ${statusLabel(finding.status)}`, "", finding.text, ""); if (finding.evidence.length) finding.evidence.forEach((evidence) => lines.push(`- **${evidence.type}** ([source](${evidence.url}))`, "", ` > ${evidence.text}`, "")); else lines.push(`- Maintainer question: ${finding.prompt}`, ""); });
|
| 167 |
+
return lines.concat(["---", "", worksheetText, "", "---", "", discussionText]).join("\n");
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
function showError(message) { notice.textContent = message; notice.hidden = false; results.hidden = true; }
|
| 171 |
+
function clearNotice() { notice.hidden = true; notice.textContent = ""; }
|
| 172 |
+
|
| 173 |
+
async function review(value) {
|
| 174 |
+
let repo;
|
| 175 |
+
try { repo = cleanRepoId(value); } catch (error) { showError(error.message); return; }
|
| 176 |
+
clearNotice(); results.hidden = true;
|
| 177 |
+
const button = form.querySelector("button"); button.disabled = true; button.textContent = "Reading public card…";
|
| 178 |
+
try {
|
| 179 |
+
const encoded = repo.split("/").map(encodeURIComponent).join("/");
|
| 180 |
+
const apiResponse = await fetch(`https://huggingface.co/api/models/${encoded}`);
|
| 181 |
+
if (apiResponse.status === 404) throw new Error("That public model repository was not found.");
|
| 182 |
+
if (!apiResponse.ok) throw new Error("The Hub API could not be reached. Try again shortly.");
|
| 183 |
+
const api = await apiResponse.json();
|
| 184 |
+
if (api.private || api.gated) throw new Error("That model is private or gated. This version reviews public cards only.");
|
| 185 |
+
const revision = api.sha || "main";
|
| 186 |
+
const cardResponse = await fetch(`https://huggingface.co/${encoded}/resolve/${encodeURIComponent(revision)}/README.md`);
|
| 187 |
+
if (!cardResponse.ok) throw new Error("The repository does not expose a README.md on its selected revision.");
|
| 188 |
+
const readme = await cardResponse.text();
|
| 189 |
+
if (readme.length > MAX_README_BYTES) throw new Error("The model card is larger than the 2 MB review limit.");
|
| 190 |
+
latestReview = analyze(repo, api, readme);
|
| 191 |
+
const worksheetText = worksheet(latestReview); const discussionText = discussion(latestReview);
|
| 192 |
+
renderSummary(latestReview); renderEvidence(latestReview);
|
| 193 |
+
worksheetPanel.replaceChildren(make("pre", "worksheet", worksheetText));
|
| 194 |
+
discussionPanel.replaceChildren(make("pre", "discussion", discussionText));
|
| 195 |
+
latestReview.download = fullReview(latestReview, worksheetText, discussionText);
|
| 196 |
+
results.hidden = false;
|
| 197 |
+
} catch (error) { showError(error.message || "An unexpected error occurred. No contribution was created or posted."); }
|
| 198 |
+
finally { button.disabled = false; button.textContent = "Review card"; }
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
form.addEventListener("submit", (event) => { event.preventDefault(); review(input.value); });
|
| 202 |
+
document.querySelectorAll("[data-example]").forEach((button) => button.addEventListener("click", () => { input.value = button.dataset.example; input.focus(); }));
|
| 203 |
+
document.querySelectorAll("[role=tab]").forEach((tab) => tab.addEventListener("click", () => { document.querySelectorAll("[role=tab]").forEach((item) => item.setAttribute("aria-selected", String(item === tab))); document.querySelectorAll("[role=tabpanel]").forEach((panel) => { panel.hidden = panel.id !== tab.dataset.tab; }); }));
|
| 204 |
+
downloadButton.addEventListener("click", () => { if (!latestReview?.download) return; const blob = new Blob([latestReview.download], { type: "text/markdown;charset=utf-8" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = `${latestReview.repo.replace("/", "--")}-review.md`; link.click(); URL.revokeObjectURL(link.href); });
|
| 205 |
+
})();
|
static-styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
:root{--ink:#172419;--muted:#647067;--paper:#fbfaf4;--line:#dfe4da;--green:#2d6a4f;--green-soft:#e7f2e9;--amber:#9a6700;--amber-soft:#fff3cf;--rose:#9f3a38;--rose-soft:#fbe8e4}*{box-sizing:border-box}body{margin:0;background:radial-gradient(circle at 12% 8%,rgba(255,214,102,.18),transparent 24rem),var(--paper);color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.shell{max-width:1080px;margin:0 auto;padding:30px 18px 56px}.hero{padding:30px 0 20px;max-width:820px}.eyebrow{color:var(--green);font-size:.75rem;font-weight:800;letter-spacing:.12em;margin:0 0 10px}.hero h1{font-family:Georgia,serif;font-size:clamp(2.5rem,7vw,5.5rem);line-height:.98;letter-spacing:-.05em;margin:0}.lede{color:#435047;font-size:1.1rem;line-height:1.65;max-width:720px;margin:20px 0 10px}.principles{display:flex;flex-wrap:wrap;gap:8px;margin-top:20px}.principles span{border:1px solid var(--line);border-radius:999px;padding:7px 11px;background:rgba(255,255,255,.7);font-size:.82rem}.input-card{border:1px solid var(--line);border-radius:18px;background:rgba(255,255,255,.82);padding:18px;box-shadow:0 16px 50px rgba(35,50,39,.07)}label{display:block;font-weight:700;margin-bottom:8px}.input-row{display:flex;gap:12px}input{min-width:0;flex:1;border:1px solid #d6d9d4;border-radius:8px;padding:13px 14px;font:inherit;color:var(--ink)}button{font:inherit;cursor:pointer}form button{background:var(--ink);border:1px solid var(--ink);border-radius:8px;color:#fff;font-weight:750;padding:0 21px}.example-label{color:var(--muted);font-size:.83rem;margin:15px 0 8px}.examples{display:flex;flex-wrap:wrap;gap:8px}.examples button{border:1px solid var(--line);border-radius:7px;background:#fff;padding:7px 10px;color:var(--ink);font-size:.82rem}.input-note{color:var(--muted);font-size:.83rem;margin:14px 0 0}.notice{border:1px solid #eab4ac;background:var(--rose-soft);color:#702e2c;padding:16px;border-radius:12px;margin-top:18px}.result-header{display:flex;gap:24px;align-items:end;justify-content:space-between;padding:28px 0 16px;border-bottom:1px solid var(--line)}.result-header h2{font-family:Georgia,serif;font-size:clamp(1.6rem,4vw,2.5rem);margin:0 0 6px;overflow-wrap:anywhere}.result-header a{color:var(--green);font-weight:650;text-decoration:none}.count-strip{display:flex;gap:10px}.count-strip div{min-width:88px;border-left:1px solid var(--line);padding-left:12px}.count-strip strong{display:block;font-family:Georgia,serif;font-size:1.8rem}.count-strip span{display:block;color:var(--muted);font-size:.72rem;text-transform:uppercase;letter-spacing:.06em}.scope-note{background:#f1f0e8;border-radius:12px;padding:13px 15px;color:#4f5b52;font-size:.88rem;margin:18px 0}.finding-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.finding-card{background:#fff;border:1px solid var(--line);border-left:4px solid var(--line);border-radius:12px;padding:15px}.finding-card.documented{border-left-color:var(--green)}.finding-card.review{border-left-color:var(--amber)}.finding-card.not-found{border-left-color:var(--rose)}.finding-topline{display:flex;gap:12px;align-items:start;justify-content:space-between}.finding-card h3{font-size:1rem;margin:0}.finding-card p{color:#536057;line-height:1.5;min-height:2.9em}.finding-card small{color:var(--muted)}.status-pill{border-radius:999px;padding:4px 8px;font-size:.7rem;font-weight:800;white-space:nowrap}.documented .status-pill{color:var(--green);background:var(--green-soft)}.review .status-pill{color:var(--amber);background:var(--amber-soft)}.not-found .status-pill{color:var(--rose);background:var(--rose-soft)}.tabs{display:flex;gap:20px;border-bottom:1px solid var(--line);margin-top:26px}.tabs button{background:none;border:0;border-bottom:2px solid transparent;padding:10px 0;color:#445147}.tabs button[aria-selected="true"]{border-color:#f97316;color:#c2410c}.tab-panel{padding:18px 0}.evidence-item{border-bottom:1px solid var(--line);padding:14px 0}.evidence-item h3{margin:0 0 6px}.evidence-item p{line-height:1.55}.evidence-item a{color:var(--green);font-weight:650}.excerpt{background:#f4f5f0;border-left:3px solid #d4d9d0;border-radius:0 6px 6px 0;color:#3f4a42;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.83rem;line-height:1.55;margin:10px 0 0;padding:10px;white-space:pre-wrap;overflow-wrap:anywhere}.worksheet,.discussion{background:#f4f5f0;border:1px solid var(--line);border-radius:10px;line-height:1.6;margin:0;overflow:auto;padding:16px;white-space:pre-wrap}.download{border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink);font-weight:700;padding:10px 13px}footer{color:var(--muted);font-size:.82rem;line-height:1.6;padding:26px 0 0}@media(max-width:720px){.result-header{display:block}.count-strip{margin-top:20px}.count-strip div{min-width:0;flex:1}.finding-grid{grid-template-columns:1fr}.input-row{display:block}.input-row button{height:46px;margin-top:10px;width:100%}.tabs{gap:12px;overflow:auto}.tabs button{font-size:.83rem;white-space:nowrap}}
|