docgrity 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +54 -0
- package/.vscodeignore +13 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/action/LICENSE +21 -0
- package/action/README.md +104 -0
- package/action/action.yml +30 -0
- package/action/bin/action.js +71 -0
- package/action/bin/docgrity.js +191 -0
- package/action/examples/docgrity.yml +61 -0
- package/action/package.json +38 -0
- package/action/src/corpus.js +110 -0
- package/action/src/issues.js +91 -0
- package/action/src/llm.js +216 -0
- package/action/src/prompts.js +65 -0
- package/action/src/report.js +281 -0
- package/action/src/scan.js +142 -0
- package/action/test/corpus.test.mjs +59 -0
- package/action/test/issues.test.mjs +89 -0
- package/action/test/report.test.mjs +76 -0
- package/docgrity_logo.png +0 -0
- package/image.png +0 -0
- package/media/icon.png +0 -0
- package/media/icon.svg +5 -0
- package/package.json +171 -0
- package/samples/api-limits.md +23 -0
- package/samples/architecture-notes.md +28 -0
- package/samples/deployment-guide.md +23 -0
- package/samples/integration-guide.md +21 -0
- package/samples/release-process.md +23 -0
- package/src/agents/assess.ts +187 -0
- package/src/agents/prompts.ts +94 -0
- package/src/agents/selectModel.ts +50 -0
- package/src/core/json.ts +58 -0
- package/src/core/prefilter.ts +56 -0
- package/src/core/slug.ts +10 -0
- package/src/core/verify.ts +15 -0
- package/src/extension.ts +142 -0
- package/src/findings/diagnostics.ts +78 -0
- package/src/findings/report.ts +68 -0
- package/src/findings/store.ts +60 -0
- package/src/findings/tree.ts +93 -0
- package/src/github/issues.ts +90 -0
- package/src/github/owners.ts +60 -0
- package/src/log.ts +22 -0
- package/src/scanner/candidates.ts +62 -0
- package/src/scanner/corpus.ts +75 -0
- package/src/scanner/scan.ts +215 -0
- package/test/candidates.test.ts +63 -0
- package/test/json.test.ts +87 -0
- package/test/prefilter.test.ts +65 -0
- package/test/slug.test.ts +31 -0
- package/test/verify.test.ts +47 -0
- package/tsconfig.json +15 -0
- package/vitest.config.mts +9 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: 22
|
|
19
|
+
cache: npm
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: npm ci
|
|
22
|
+
- name: Compile (strict TypeScript)
|
|
23
|
+
run: npm run compile
|
|
24
|
+
- name: Unit tests
|
|
25
|
+
run: npm test
|
|
26
|
+
- name: Action unit tests (zero-dependency, node:test)
|
|
27
|
+
working-directory: action
|
|
28
|
+
run: npm test
|
|
29
|
+
- name: Dependency vulnerability audit
|
|
30
|
+
run: npm audit --audit-level=high
|
|
31
|
+
- name: Secret pattern scan (source only)
|
|
32
|
+
run: |
|
|
33
|
+
! grep -rInE "(sk-[A-Za-z0-9]{20,}|AIza[A-Za-z0-9_-]{30,}|ghp_[A-Za-z0-9]{30,}|github_pat_[A-Za-z0-9_]{30,})" src/ action/src/ action/bin/ \
|
|
34
|
+
&& echo "No hardcoded credentials found."
|
|
35
|
+
- name: Package extension
|
|
36
|
+
run: npx --yes @vscode/vsce package --no-dependencies
|
|
37
|
+
- name: Upload .vsix artifact
|
|
38
|
+
uses: actions/upload-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: docgrity-vsix
|
|
41
|
+
path: '*.vsix'
|
|
42
|
+
|
|
43
|
+
codeql:
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
permissions:
|
|
46
|
+
contents: read
|
|
47
|
+
security-events: write
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
- uses: github/codeql-action/init@v3
|
|
51
|
+
with:
|
|
52
|
+
languages: javascript-typescript
|
|
53
|
+
queries: security-and-quality
|
|
54
|
+
- uses: github/codeql-action/analyze@v3
|
package/.vscodeignore
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ujjavala
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Docgrity for VS Code
|
|
2
|
+
|
|
3
|
+
[](https://marketplace.visualstudio.com/items?itemName=ujjavala.docgrity)
|
|
4
|
+
[](https://github.com/ujjavala/docgrity-vscode/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
**Find where your repository's docs disagree with themselves — and raise a GitHub issue to get it fixed.**
|
|
7
|
+
|
|
8
|
+
**Install:** search “Docgrity” in the Extensions view, or `code --install-extension ujjavala.docgrity`
|
|
9
|
+
|
|
10
|
+
The repo-docs sibling of the [Docgrity Confluence app](https://ujjavala.github.io/docgrity-site/).
|
|
11
|
+
Scoped deliberately: **markdown files only** (`**/*.md` — READMEs, ADRs, runbooks, guides).
|
|
12
|
+
|
|
13
|
+
This repo contains all three repo-docs surfaces:
|
|
14
|
+
|
|
15
|
+
| Surface | Where | Acts? |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| **VS Code extension** (this root) | interactive scans in the editor | raises issues, human-approved one at a time |
|
|
18
|
+
| **GitHub Action** ([action/](action/)) | CI: schedule + PRs | opt-in deduped issues, job summary, HTML report |
|
|
19
|
+
| **Local CLI** ([action/bin/docgrity.js](action/bin/docgrity.js)) | your terminal | **read-only** report dashboard, no actions |
|
|
20
|
+
|
|
21
|
+
See [action/README.md](action/README.md) for Action and CLI usage
|
|
22
|
+
(`uses: ujjavala/docgrity-vscode/action@main`).
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
1. **Scan** — `Docgrity: Scan repository docs` collects your markdown files, picks
|
|
27
|
+
candidate pairs locally with TF-IDF (no network), then asks the LLM to assess:
|
|
28
|
+
- **Contradictions** — conflicting factual claims across two docs
|
|
29
|
+
- **Duplicates** — substantially overlapping docs that should be merged
|
|
30
|
+
- **Open questions** — unresolved TBD/TODO/"who owns this?" buried in docs
|
|
31
|
+
2. **Review** — findings appear in the Docgrity view with evidence excerpts; each
|
|
32
|
+
excerpt is a click away from the exact spot in the file, and shows as a
|
|
33
|
+
diagnostic squiggle. Every finding records the model + prompt version.
|
|
34
|
+
3. **Act** — right-click a finding → **Raise GitHub issue**. Docgrity drafts the
|
|
35
|
+
issue (title, evidence, suggested next step, *potential* owner from git history),
|
|
36
|
+
shows you the draft, and only creates it after you approve. The issue is labelled
|
|
37
|
+
`docgrity` and `docgrity:<type>`.
|
|
38
|
+
|
|
39
|
+
## Zero cost, zero keys
|
|
40
|
+
|
|
41
|
+
- All LLM calls go through **your own GitHub Copilot subscription** via the VS Code
|
|
42
|
+
Language Model API. No API keys, no servers, no telemetry.
|
|
43
|
+
- Issue creation uses VS Code's built-in GitHub sign-in.
|
|
44
|
+
- Candidate selection is local TF-IDF — the LLM only sees the top pairs.
|
|
45
|
+
|
|
46
|
+
## Modes: report-only vs report + issues
|
|
47
|
+
|
|
48
|
+
| `docgrity.mode` | Behaviour |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `report-and-issue` (default) | Scan, review findings, and raise GitHub issues — each previewed and human-approved. |
|
|
51
|
+
| `report-only` | Scan and review only. The *Raise GitHub issue* action is hidden and blocked — the extension is guaranteed to never post anywhere. Good for client repos, compliance-sensitive environments, or just reading. |
|
|
52
|
+
|
|
53
|
+
Set it in Settings → search “docgrity mode”, or in `.vscode/settings.json`:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{ "docgrity.mode": "report-only" }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Per-workspace settings win over user settings, so you can default to report-only
|
|
60
|
+
globally and enable issues only in repos you own.
|
|
61
|
+
|
|
62
|
+
## Choosing which checks run
|
|
63
|
+
|
|
64
|
+
Each check is a separate toggle — run any combination:
|
|
65
|
+
|
|
66
|
+
| Setting | Default | What it does |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `docgrity.checks.duplicates` | `true` | Pairwise duplicate detection |
|
|
69
|
+
| `docgrity.checks.contradictions` | `true` | Pairwise contradiction detection |
|
|
70
|
+
| `docgrity.checks.openQuestions` | `true` | Per-doc unresolved-question detection |
|
|
71
|
+
|
|
72
|
+
These combine freely with any model (`docgrity.model.*`) and either mode
|
|
73
|
+
(`docgrity.mode`). Disabling checks also speeds up scans: pair selection is
|
|
74
|
+
skipped entirely when both pairwise checks are off.
|
|
75
|
+
|
|
76
|
+
## Scan performance
|
|
77
|
+
|
|
78
|
+
- When both pairwise checks are enabled they run as a **single combined LLM
|
|
79
|
+
call per pair** (the model reads each pair once, not twice).
|
|
80
|
+
- Docs with no open-question signals (no TODO/TBD/`???`/unanswered questions)
|
|
81
|
+
are **pre-filtered out** before any LLM call.
|
|
82
|
+
- Assessments run with **bounded concurrency** (4 at a time).
|
|
83
|
+
- For large repos, tune `docgrity.maxFiles`, `docgrity.maxPairs`, and
|
|
84
|
+
`docgrity.include` to narrow the corpus.
|
|
85
|
+
|
|
86
|
+
## Choosing your model (Copilot, Claude, GPT, local llama…)
|
|
87
|
+
|
|
88
|
+
Run **`Docgrity: Select AI model`** from the command palette — it lists every model
|
|
89
|
+
VS Code exposes and saves your choice. Or set it manually:
|
|
90
|
+
|
|
91
|
+
| Setting | Meaning | Default |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `docgrity.model.vendor` | `vscode.lm` vendor id (`copilot` covers Copilot + BYOK models; empty = any) | `copilot` |
|
|
94
|
+
| `docgrity.model.family` | preferred model family, e.g. `gpt-4o`, `claude-sonnet-4.5`, `llama3.1` (empty = first available) | `""` |
|
|
95
|
+
|
|
96
|
+
**Options, in order of simplicity:**
|
|
97
|
+
|
|
98
|
+
1. **Copilot (default)** — sign in to GitHub Copilot; nothing to configure.
|
|
99
|
+
2. **Claude / GPT / Gemini via Copilot** — any model enabled in Copilot's model picker
|
|
100
|
+
is available; set `docgrity.model.family` (e.g. `claude-sonnet-4.5`) or use
|
|
101
|
+
*Select AI model*.
|
|
102
|
+
3. **Local Ollama** — install [Ollama](https://ollama.com), `ollama pull llama3.1`,
|
|
103
|
+
then in Copilot Chat → **Manage models** → add the Ollama model. It registers under
|
|
104
|
+
the `copilot` vendor; pick it with *Select AI model*. Fully local — no doc content
|
|
105
|
+
leaves your machine.
|
|
106
|
+
4. **Remote Ollama over a Cloudflare Tunnel** — if your model runs on another box
|
|
107
|
+
(home server, GPU rig):
|
|
108
|
+
```bash
|
|
109
|
+
# on the machine running Ollama
|
|
110
|
+
cloudflared tunnel --url http://localhost:11434
|
|
111
|
+
```
|
|
112
|
+
Point Copilot's Manage models → Ollama endpoint at the generated
|
|
113
|
+
`https://….trycloudflare.com` URL. Note: quick tunnels get a **new URL on every
|
|
114
|
+
restart** — re-update the endpoint each time, or create a **named tunnel** with your
|
|
115
|
+
own domain for a stable URL (`cloudflared tunnel create …`). Protect a named tunnel
|
|
116
|
+
with Cloudflare Access — an open LLM endpoint is abusable.
|
|
117
|
+
|
|
118
|
+
Small local models fail Docgrity's strict-JSON validation more often than hosted
|
|
119
|
+
ones; failed responses are rejected safely (never mis-recorded) — expect fewer
|
|
120
|
+
findings rather than wrong ones. 8B+ instruct models work best.
|
|
121
|
+
|
|
122
|
+
## Design principles (shared with the Forge app)
|
|
123
|
+
|
|
124
|
+
- Typed JSON outputs only — model responses are validated in code, never trusted prose.
|
|
125
|
+
- Every finding requires verbatim evidence, verified against the source file
|
|
126
|
+
(hallucinated quotes are dropped).
|
|
127
|
+
- Ownership is always *potential* (last git author), never asserted.
|
|
128
|
+
- Nothing is posted anywhere without explicit human approval.
|
|
129
|
+
- Doc content is untrusted input — it cannot override agent instructions.
|
|
130
|
+
|
|
131
|
+
## Requirements
|
|
132
|
+
|
|
133
|
+
- VS Code 1.95+, an active GitHub Copilot subscription, a workspace with a GitHub
|
|
134
|
+
`origin` remote (for issue creation).
|
|
135
|
+
|
|
136
|
+
## Development
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npm install
|
|
140
|
+
npm run compile
|
|
141
|
+
# F5 in VS Code to launch the Extension Development Host
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Settings
|
|
145
|
+
|
|
146
|
+
| Setting | Default | Purpose |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `docgrity.include` | `**/*.md` | Docs glob (markdown only by design) |
|
|
149
|
+
| `docgrity.exclude` | `**/{node_modules,…}/**` | Excluded paths |
|
|
150
|
+
| `docgrity.maxFiles` / `docgrity.maxPairs` | 200 / 25 | Scan caps |
|
|
151
|
+
| `docgrity.thresholds.*` | 0.75 / 0.7 / 0.6 | Confidence gates per finding type |
|
package/action/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ujjavala
|
|
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.
|
package/action/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Docgrity Action & CLI
|
|
2
|
+
|
|
3
|
+
**Continuous doc-integrity for your repo's markdown: contradictions, duplicates and open
|
|
4
|
+
questions — as a GitHub Action (with deduplicated issues) and a read-only local CLI.**
|
|
5
|
+
|
|
6
|
+
Part of the Docgrity family:
|
|
7
|
+
|
|
8
|
+
| Surface | Job | Acts? |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| [Confluence app](https://ujjavala.github.io/docgrity-site/) | wiki integrity | comments (human-approved) |
|
|
11
|
+
| VS Code extension | interactive repo-doc scans | raises issues (human-approved) |
|
|
12
|
+
| **This Action** | continuous CI enforcement | issues (opt-in, deduped, capped) + report |
|
|
13
|
+
| **This CLI** | local observation | **read-only** — report dashboard only |
|
|
14
|
+
|
|
15
|
+
## GitHub Action
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
- uses: ujjavala/docgrity-vscode/action@main
|
|
19
|
+
env:
|
|
20
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
21
|
+
with:
|
|
22
|
+
provider: github-models # free — uses GITHUB_TOKEN, no API key
|
|
23
|
+
create_issues: 'true' # opt-in; default false
|
|
24
|
+
max_new_issues: 5
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Full example with weekly schedule, PR trigger and Pages report publishing:
|
|
28
|
+
[examples/docgrity.yml](examples/docgrity.yml).
|
|
29
|
+
|
|
30
|
+
What it does per run:
|
|
31
|
+
|
|
32
|
+
1. Collects markdown docs (`**/*.md`, capped), selects candidate pairs locally (TF-IDF),
|
|
33
|
+
assesses with the LLM using versioned prompts and typed-JSON validation, verifies
|
|
34
|
+
every evidence excerpt verbatim against the source (hallucination guard).
|
|
35
|
+
2. Writes a **job summary** table and a **static HTML report** (`docgrity-report/`)
|
|
36
|
+
with evidence, links to docs on GitHub, and *potential* owners from git history.
|
|
37
|
+
3. **Opt-in** (`create_issues: true`): syncs GitHub issues **deduplicated by a stable
|
|
38
|
+
finding fingerprint** — new findings create issues (capped per run), unchanged ones
|
|
39
|
+
are left alone, resolved ones are auto-closed with a comment. Labels: `docgrity`,
|
|
40
|
+
`docgrity:<type>`.
|
|
41
|
+
|
|
42
|
+
### Providers
|
|
43
|
+
|
|
44
|
+
| provider | key | cost |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `github-models` (default in CI) | none — uses `GITHUB_TOKEN` with `models: read` | free (being retired by GitHub — prefer a BYO provider) |
|
|
47
|
+
| `gemini` / `openai` / `anthropic` | `api_key` input (use a repo secret) | your key |
|
|
48
|
+
| `ollama` | none — local or tunnelled endpoint | free, fully private |
|
|
49
|
+
|
|
50
|
+
## Local CLI (read-only)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm i -g docgrity
|
|
54
|
+
docgrity scan --open
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
or without installing: `npx docgrity scan --open` (from a repo checkout:
|
|
58
|
+
`npx github:ujjavala/docgrity-vscode scan --open`).
|
|
59
|
+
|
|
60
|
+
Runs the same scan locally and opens the **report dashboard**: findings, evidence,
|
|
61
|
+
doc links and potential owners. **The CLI never raises issues or takes any action** —
|
|
62
|
+
by design, local scans observe; only CI (explicitly opted in) acts.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Usage: docgrity scan [options]
|
|
66
|
+
|
|
67
|
+
--dir <path> Directory to scan (default: .)
|
|
68
|
+
--out <path> Report output directory (default: docgrity-report)
|
|
69
|
+
--open Open the HTML report when done
|
|
70
|
+
|
|
71
|
+
--checks <list> duplicates, contradictions, open-questions — any combination
|
|
72
|
+
--max-files <n> Max markdown files (default: 200)
|
|
73
|
+
--max-pairs <n> Max document pairs (default: 25)
|
|
74
|
+
--threshold-duplicate / --threshold-contradiction / --threshold-open-question <0..1>
|
|
75
|
+
|
|
76
|
+
--provider <p> ollama | gemini | openai | anthropic | github-models
|
|
77
|
+
--model <m> Model name
|
|
78
|
+
--endpoint <url> Ollama endpoint (default http://localhost:11434)
|
|
79
|
+
|
|
80
|
+
--version, -v Installed version + latest on npm
|
|
81
|
+
--help, -h Full help
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Provider auto-detection: `DOCGRITY_API_KEY` set → `gemini`; else `GITHUB_TOKEN` →
|
|
85
|
+
`github-models`; else → `ollama` (local, fully private — nothing leaves your machine).
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
docgrity scan --checks contradictions # one check only
|
|
91
|
+
docgrity scan --checks duplicates,open-questions --max-pairs 10
|
|
92
|
+
docgrity scan --provider ollama --model llama3.1:8b # fully local
|
|
93
|
+
DOCGRITY_API_KEY=... docgrity scan --provider gemini --open
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Design principles (shared across all Docgrity surfaces)
|
|
97
|
+
|
|
98
|
+
- Typed JSON outputs only; model responses validated in code.
|
|
99
|
+
- Every finding requires verbatim evidence, verified against the source file.
|
|
100
|
+
- Ownership is always *potential* (last git author), never asserted.
|
|
101
|
+
- Action-taking is opt-in, capped, and auditable (issue trailer records model +
|
|
102
|
+
prompt version + fingerprint).
|
|
103
|
+
- Doc content is untrusted input — it cannot override agent instructions.
|
|
104
|
+
- Zero dependencies; plain Node 20+ ESM.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: 'Docgrity doc-integrity scan'
|
|
2
|
+
description: >-
|
|
3
|
+
Finds contradictions, duplicates and open questions across repository markdown
|
|
4
|
+
docs. Writes a job summary and a static HTML report; optionally raises
|
|
5
|
+
deduplicated GitHub issues.
|
|
6
|
+
branding:
|
|
7
|
+
icon: 'file-text'
|
|
8
|
+
color: 'blue'
|
|
9
|
+
inputs:
|
|
10
|
+
provider:
|
|
11
|
+
description: 'LLM provider: github-models (default, uses GITHUB_TOKEN), gemini, openai, anthropic'
|
|
12
|
+
default: 'github-models'
|
|
13
|
+
api_key:
|
|
14
|
+
description: 'API key for gemini/openai/anthropic (pass a repo secret). Not needed for github-models.'
|
|
15
|
+
default: ''
|
|
16
|
+
create_issues:
|
|
17
|
+
description: 'Create/update deduplicated GitHub issues for findings (opt-in)'
|
|
18
|
+
default: 'false'
|
|
19
|
+
max_new_issues:
|
|
20
|
+
description: 'Cap on new issues created per run'
|
|
21
|
+
default: '5'
|
|
22
|
+
include:
|
|
23
|
+
description: 'Markdown glob'
|
|
24
|
+
default: '**/*.md'
|
|
25
|
+
out_dir:
|
|
26
|
+
description: 'Report output directory'
|
|
27
|
+
default: 'docgrity-report'
|
|
28
|
+
runs:
|
|
29
|
+
using: 'node20'
|
|
30
|
+
main: 'bin/action.js'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Action entrypoint. Reads inputs from env (INPUT_*), runs the scan,
|
|
3
|
+
* writes the job summary + HTML report, and (opt-in) syncs deduplicated issues.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdir, writeFile, appendFile } from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { makeClient } from '../src/llm.js';
|
|
8
|
+
import { runScan } from '../src/scan.js';
|
|
9
|
+
import { renderReport, renderSummaryMarkdown } from '../src/report.js';
|
|
10
|
+
import { syncIssues } from '../src/issues.js';
|
|
11
|
+
|
|
12
|
+
const input = (name, fallback = '') =>
|
|
13
|
+
(process.env[`INPUT_${name.toUpperCase()}`] ?? fallback).toString().trim() || fallback;
|
|
14
|
+
|
|
15
|
+
const root = process.env.GITHUB_WORKSPACE ?? process.cwd();
|
|
16
|
+
const token = process.env.GITHUB_TOKEN ?? process.env.INPUT_GITHUB_TOKEN ?? '';
|
|
17
|
+
const slug = process.env.GITHUB_REPOSITORY ?? '';
|
|
18
|
+
const branch = (process.env.GITHUB_REF_NAME ?? 'main').replace(/^refs\/heads\//, '');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const client = makeClient({
|
|
22
|
+
provider: input('provider', 'github-models'),
|
|
23
|
+
apiKey: input('api_key'),
|
|
24
|
+
githubToken: token,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(`Docgrity scan on ${slug} (${branch})`);
|
|
28
|
+
const { findings, stats } = await runScan(root, { client, log: (m) => console.log(m) });
|
|
29
|
+
|
|
30
|
+
// Opt-in issue sync (deduped by fingerprint, capped, auto-close resolved).
|
|
31
|
+
let issueResult = null;
|
|
32
|
+
if (input('create_issues', 'false') === 'true') {
|
|
33
|
+
if (!token) throw new Error('create_issues requires GITHUB_TOKEN with issues: write');
|
|
34
|
+
issueResult = await syncIssues({
|
|
35
|
+
client,
|
|
36
|
+
token,
|
|
37
|
+
slug,
|
|
38
|
+
findings,
|
|
39
|
+
maxNewIssues: Number(input('max_new_issues', '5')) || 5,
|
|
40
|
+
log: (m) => console.log(m),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Static HTML report + raw findings.
|
|
45
|
+
const outDir = path.resolve(root, input('out_dir', 'docgrity-report'));
|
|
46
|
+
await mkdir(outDir, { recursive: true });
|
|
47
|
+
await writeFile(path.join(outDir, 'index.html'), renderReport({ findings, stats, repoSlug: slug, branch }));
|
|
48
|
+
await writeFile(path.join(outDir, 'findings.json'), JSON.stringify({ findings, stats }, null, 2));
|
|
49
|
+
|
|
50
|
+
// Job summary.
|
|
51
|
+
if (process.env.GITHUB_STEP_SUMMARY) {
|
|
52
|
+
let summary = renderSummaryMarkdown({ findings, stats });
|
|
53
|
+
if (issueResult) {
|
|
54
|
+
summary += `\n\nIssues: ${issueResult.created.length} created, ${issueResult.closed.length} closed, ${issueResult.unchanged} unchanged${issueResult.skipped ? `, ${issueResult.skipped} skipped (cap)` : ''}.`;
|
|
55
|
+
}
|
|
56
|
+
await appendFile(process.env.GITHUB_STEP_SUMMARY, summary + '\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Outputs.
|
|
60
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
61
|
+
await appendFile(
|
|
62
|
+
process.env.GITHUB_OUTPUT,
|
|
63
|
+
`findings=${findings.length}\nreport_dir=${outDir}\n`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`Done: ${findings.length} finding(s). Report in ${outDir}`);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`::error::Docgrity scan failed: ${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Docgrity local CLI — read-only. Runs a scan and produces the HTML report
|
|
4
|
+
* dashboard (findings, evidence, doc links, potential owners). Deliberately
|
|
5
|
+
* cannot raise issues or take any action: local scans observe, CI acts.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* docgrity scan [--provider gemini|openai|anthropic|github-models] [--model m]
|
|
9
|
+
* [--dir .] [--out docgrity-report] [--open]
|
|
10
|
+
* Keys via env: DOCGRITY_API_KEY (BYO providers) or GITHUB_TOKEN (github-models).
|
|
11
|
+
*/
|
|
12
|
+
import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { execFile } from 'child_process';
|
|
16
|
+
import { makeClient } from '../src/llm.js';
|
|
17
|
+
import { runScan } from '../src/scan.js';
|
|
18
|
+
import { renderReport } from '../src/report.js';
|
|
19
|
+
import { githubRepoSlug, defaultBranch } from '../src/corpus.js';
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const args = { _: [] };
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
if (argv[i].startsWith('--')) {
|
|
25
|
+
const key = argv[i].slice(2);
|
|
26
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('--')) args[key] = argv[++i];
|
|
27
|
+
else args[key] = true;
|
|
28
|
+
} else args._.push(argv[i]);
|
|
29
|
+
}
|
|
30
|
+
return args;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const args = parseArgs(process.argv.slice(2));
|
|
34
|
+
|
|
35
|
+
const HELP = `docgrity — doc-integrity scans for repository markdown (read-only)
|
|
36
|
+
|
|
37
|
+
Finds contradictions, duplicates, and open questions across your repo's
|
|
38
|
+
markdown docs, and writes an HTML report + findings.json. The CLI never
|
|
39
|
+
posts anything anywhere — it is report-only by design.
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
docgrity scan [options]
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
--dir <path> Directory to scan (default: .)
|
|
46
|
+
--out <path> Report output directory (default: docgrity-report)
|
|
47
|
+
--open Open the HTML report when done
|
|
48
|
+
|
|
49
|
+
--checks <list> Comma-separated checks to run (default: all)
|
|
50
|
+
duplicates, contradictions, open-questions
|
|
51
|
+
--max-files <n> Max markdown files to scan (default: 200)
|
|
52
|
+
--max-pairs <n> Max document pairs to assess (default: 25)
|
|
53
|
+
--threshold-duplicate <0..1> Min confidence, duplicates (default: 0.75)
|
|
54
|
+
--threshold-contradiction <0..1> Min confidence, contradictions (default: 0.7)
|
|
55
|
+
--threshold-open-question <0..1> Min confidence, open questions (default: 0.6)
|
|
56
|
+
|
|
57
|
+
--provider <p> ollama | gemini | openai | anthropic | github-models
|
|
58
|
+
(default: auto — see below)
|
|
59
|
+
--model <m> Model name (provider-specific default otherwise)
|
|
60
|
+
--endpoint <url> Ollama endpoint (default: http://localhost:11434,
|
|
61
|
+
or DOCGRITY_OLLAMA_URL; supports tunnelled remotes)
|
|
62
|
+
|
|
63
|
+
--version, -v Print installed version (and latest on npm)
|
|
64
|
+
--help, -h Show this help
|
|
65
|
+
|
|
66
|
+
Environment:
|
|
67
|
+
DOCGRITY_API_KEY API key for gemini / openai / anthropic
|
|
68
|
+
DOCGRITY_OLLAMA_URL Default Ollama endpoint
|
|
69
|
+
GITHUB_TOKEN / GH_TOKEN Token for the github-models provider
|
|
70
|
+
|
|
71
|
+
Provider auto-detection (when --provider is omitted):
|
|
72
|
+
DOCGRITY_API_KEY set → gemini; else GITHUB_TOKEN set → github-models;
|
|
73
|
+
else → ollama (local, fully private).
|
|
74
|
+
|
|
75
|
+
Examples:
|
|
76
|
+
docgrity scan --open
|
|
77
|
+
docgrity scan --checks contradictions
|
|
78
|
+
docgrity scan --checks duplicates,open-questions --max-pairs 10
|
|
79
|
+
docgrity scan --provider ollama --model llama3.1:8b
|
|
80
|
+
docgrity scan --provider gemini # DOCGRITY_API_KEY=...
|
|
81
|
+
docgrity scan --dir ./docs --out /tmp/report --threshold-contradiction 0.8
|
|
82
|
+
|
|
83
|
+
Docs: https://ujjavala.github.io/docgrity-vscode-site/
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
async function localVersion() {
|
|
87
|
+
const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
88
|
+
return JSON.parse(await readFile(pkgPath, 'utf8')).version;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function latestVersion() {
|
|
92
|
+
try {
|
|
93
|
+
const ctrl = new AbortController();
|
|
94
|
+
const t = setTimeout(() => ctrl.abort(), 3000);
|
|
95
|
+
const res = await fetch('https://registry.npmjs.org/docgrity/latest', { signal: ctrl.signal });
|
|
96
|
+
clearTimeout(t);
|
|
97
|
+
if (!res.ok) return undefined;
|
|
98
|
+
return (await res.json()).version;
|
|
99
|
+
} catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (args.help || args.h || args._[0] === 'help') {
|
|
105
|
+
console.log(HELP);
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (args.version || args.v) {
|
|
110
|
+
const installed = await localVersion();
|
|
111
|
+
const latest = await latestVersion();
|
|
112
|
+
console.log(`docgrity ${installed}`);
|
|
113
|
+
if (latest && latest !== installed) {
|
|
114
|
+
console.log(`Latest on npm: ${latest} — update with: npm i -g docgrity`);
|
|
115
|
+
} else if (latest) {
|
|
116
|
+
console.log('Up to date.');
|
|
117
|
+
}
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (args._[0] !== 'scan') {
|
|
122
|
+
console.log(HELP);
|
|
123
|
+
process.exit(args._[0] ? 1 : 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const CHECK_NAMES = { duplicates: 'duplicates', contradictions: 'contradictions', 'open-questions': 'openQuestions' };
|
|
127
|
+
function parseChecks(value) {
|
|
128
|
+
if (!value || value === true) return { duplicates: true, contradictions: true, openQuestions: true };
|
|
129
|
+
const checks = { duplicates: false, contradictions: false, openQuestions: false };
|
|
130
|
+
for (const name of String(value).split(',').map((s) => s.trim()).filter(Boolean)) {
|
|
131
|
+
const key = CHECK_NAMES[name];
|
|
132
|
+
if (!key) {
|
|
133
|
+
console.error(`docgrity: unknown check "${name}" (valid: ${Object.keys(CHECK_NAMES).join(', ')})`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
checks[key] = true;
|
|
137
|
+
}
|
|
138
|
+
return checks;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const numArg = (v, fallback) => (v === undefined || v === true ? fallback : Number(v));
|
|
142
|
+
|
|
143
|
+
const root = path.resolve(args.dir ?? '.');
|
|
144
|
+
const outDir = path.resolve(root, args.out ?? 'docgrity-report');
|
|
145
|
+
const provider =
|
|
146
|
+
args.provider ??
|
|
147
|
+
(process.env.DOCGRITY_API_KEY ? 'gemini' : process.env.GITHUB_TOKEN || process.env.GH_TOKEN ? 'github-models' : 'ollama');
|
|
148
|
+
const checks = parseChecks(args.checks);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const client = makeClient({
|
|
152
|
+
provider,
|
|
153
|
+
apiKey: process.env.DOCGRITY_API_KEY,
|
|
154
|
+
githubToken: process.env.GITHUB_TOKEN || process.env.GH_TOKEN,
|
|
155
|
+
model: args.model,
|
|
156
|
+
endpoint: args.endpoint,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const enabled = Object.entries(checks).filter(([, on]) => on).map(([k]) => k).join(', ');
|
|
160
|
+
console.log(`Docgrity local scan (read-only) — provider: ${provider}; checks: ${enabled}`);
|
|
161
|
+
const { findings, stats } = await runScan(root, {
|
|
162
|
+
client,
|
|
163
|
+
checks,
|
|
164
|
+
maxFiles: numArg(args['max-files'], 200),
|
|
165
|
+
maxPairs: numArg(args['max-pairs'], 25),
|
|
166
|
+
thresholds: {
|
|
167
|
+
duplicate: numArg(args['threshold-duplicate'], 0.75),
|
|
168
|
+
contradiction: numArg(args['threshold-contradiction'], 0.7),
|
|
169
|
+
openQuestion: numArg(args['threshold-open-question'], 0.6),
|
|
170
|
+
},
|
|
171
|
+
log: (m) => console.log(` ${m}`),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const slug = await githubRepoSlug(root);
|
|
175
|
+
const branch = slug ? await defaultBranch(root) : undefined;
|
|
176
|
+
const html = renderReport({ findings, stats, repoSlug: slug, branch });
|
|
177
|
+
|
|
178
|
+
await mkdir(outDir, { recursive: true });
|
|
179
|
+
const reportPath = path.join(outDir, 'index.html');
|
|
180
|
+
await writeFile(reportPath, html);
|
|
181
|
+
await writeFile(path.join(outDir, 'findings.json'), JSON.stringify({ findings, stats }, null, 2));
|
|
182
|
+
|
|
183
|
+
console.log(`\n${findings.length} finding(s) across ${stats.docs} docs.`);
|
|
184
|
+
console.log(`Report: ${reportPath}`);
|
|
185
|
+
console.log('Note: local scans are read-only — review owners and evidence in the report; issues are only raised by CI (opt-in).');
|
|
186
|
+
|
|
187
|
+
if (args.open) execFile(process.platform === 'darwin' ? 'open' : 'xdg-open', [reportPath]);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
console.error(`docgrity: ${err.message}`);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|