docgrity 0.1.2 → 0.1.3
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/README.md +80 -127
- package/package.json +31 -164
- package/.github/workflows/ci.yml +0 -54
- package/.vscodeignore +0 -13
- package/action/LICENSE +0 -21
- package/action/README.md +0 -104
- package/action/examples/docgrity.yml +0 -61
- package/action/package.json +0 -38
- package/action/test/corpus.test.mjs +0 -59
- package/action/test/issues.test.mjs +0 -89
- package/action/test/report.test.mjs +0 -76
- package/docgrity_logo.png +0 -0
- package/image.png +0 -0
- package/media/icon.png +0 -0
- package/media/icon.svg +0 -5
- package/samples/api-limits.md +0 -23
- package/samples/architecture-notes.md +0 -28
- package/samples/deployment-guide.md +0 -23
- package/samples/integration-guide.md +0 -21
- package/samples/release-process.md +0 -23
- package/src/agents/assess.ts +0 -187
- package/src/agents/prompts.ts +0 -94
- package/src/agents/selectModel.ts +0 -50
- package/src/core/json.ts +0 -58
- package/src/core/prefilter.ts +0 -56
- package/src/core/slug.ts +0 -10
- package/src/core/verify.ts +0 -15
- package/src/extension.ts +0 -142
- package/src/findings/diagnostics.ts +0 -78
- package/src/findings/report.ts +0 -68
- package/src/findings/store.ts +0 -60
- package/src/findings/tree.ts +0 -93
- package/src/github/issues.ts +0 -90
- package/src/github/owners.ts +0 -60
- package/src/log.ts +0 -22
- package/src/scanner/candidates.ts +0 -62
- package/src/scanner/corpus.ts +0 -75
- package/src/scanner/scan.ts +0 -215
- package/test/candidates.test.ts +0 -63
- package/test/json.test.ts +0 -87
- package/test/prefilter.test.ts +0 -65
- package/test/slug.test.ts +0 -31
- package/test/verify.test.ts +0 -47
- package/tsconfig.json +0 -15
- package/vitest.config.mts +0 -9
- /package/{action/action.yml → action.yml} +0 -0
- /package/{action/bin → bin}/action.js +0 -0
- /package/{action/bin → bin}/docgrity.js +0 -0
- /package/{action/src → src}/corpus.js +0 -0
- /package/{action/src → src}/issues.js +0 -0
- /package/{action/src → src}/llm.js +0 -0
- /package/{action/src → src}/prompts.js +0 -0
- /package/{action/src → src}/report.js +0 -0
- /package/{action/src → src}/scan.js +0 -0
package/test/json.test.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { extractJson, num, str, validateEvidence } from '../src/core/json';
|
|
3
|
-
|
|
4
|
-
describe('extractJson', () => {
|
|
5
|
-
it('parses a bare JSON object', () => {
|
|
6
|
-
expect(extractJson('{"a":1}')).toEqual({ a: 1 });
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
it('parses JSON wrapped in prose and code fences', () => {
|
|
10
|
-
const text = 'Sure! Here is the result:\n```json\n{"is_duplicate": true, "confidence": 0.9}\n```\nLet me know.';
|
|
11
|
-
expect(extractJson(text)).toEqual({ is_duplicate: true, confidence: 0.9 });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('handles nested objects and braces inside strings', () => {
|
|
15
|
-
const text = 'prefix {"summary": "uses {braces} and \\"quotes\\"", "detail": {"x": 1}} suffix {ignored}';
|
|
16
|
-
expect(extractJson(text)).toEqual({
|
|
17
|
-
summary: 'uses {braces} and "quotes"',
|
|
18
|
-
detail: { x: 1 },
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('throws when there is no JSON object', () => {
|
|
23
|
-
expect(() => extractJson('no json here')).toThrow(/No JSON object/);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('throws on unbalanced JSON', () => {
|
|
27
|
-
expect(() => extractJson('{"a": 1')).toThrow(/Unbalanced/);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('num', () => {
|
|
32
|
-
it('clamps to [0,1] by default', () => {
|
|
33
|
-
expect(num(2)).toBe(1);
|
|
34
|
-
expect(num(-1)).toBe(0);
|
|
35
|
-
expect(num(0.5)).toBe(0.5);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('coerces non-numbers to 0', () => {
|
|
39
|
-
expect(num('not a number')).toBe(0);
|
|
40
|
-
expect(num(undefined)).toBe(0);
|
|
41
|
-
expect(num(null)).toBe(0);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('accepts numeric strings', () => {
|
|
45
|
-
expect(num('0.7')).toBe(0.7);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('str', () => {
|
|
50
|
-
it('passes strings through', () => {
|
|
51
|
-
expect(str('hello')).toBe('hello');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('coerces null/undefined to empty string', () => {
|
|
55
|
-
expect(str(null)).toBe('');
|
|
56
|
-
expect(str(undefined)).toBe('');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('stringifies other values', () => {
|
|
60
|
-
expect(str(42)).toBe('42');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('validateEvidence', () => {
|
|
65
|
-
it('normalises page to A unless explicitly B', () => {
|
|
66
|
-
const out = validateEvidence([
|
|
67
|
-
{ page: 'B', excerpt: 'x' },
|
|
68
|
-
{ page: 'C', excerpt: 'y' },
|
|
69
|
-
{ excerpt: 'z' },
|
|
70
|
-
]);
|
|
71
|
-
expect(out.map((e) => e.page)).toEqual(['B', 'A', 'A']);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('drops entries with empty excerpts', () => {
|
|
75
|
-
expect(validateEvidence([{ page: 'A', excerpt: '' }, { page: 'A' }])).toEqual([]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('truncates excerpts to 1000 chars', () => {
|
|
79
|
-
const out = validateEvidence([{ page: 'A', excerpt: 'a'.repeat(2000) }]);
|
|
80
|
-
expect(out[0].excerpt).toHaveLength(1000);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('tolerates non-array input', () => {
|
|
84
|
-
expect(validateEvidence(null)).toEqual([]);
|
|
85
|
-
expect(validateEvidence('nope')).toEqual([]);
|
|
86
|
-
});
|
|
87
|
-
});
|
package/test/prefilter.test.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { hasOpenQuestionSignals, mapLimit } from '../src/core/prefilter';
|
|
3
|
-
|
|
4
|
-
describe('hasOpenQuestionSignals', () => {
|
|
5
|
-
it('detects TODO/TBD/FIXME markers', () => {
|
|
6
|
-
expect(hasOpenQuestionSignals('Deploy steps\nTODO: add rollback')).toBe(true);
|
|
7
|
-
expect(hasOpenQuestionSignals('Owner: TBD')).toBe(true);
|
|
8
|
-
expect(hasOpenQuestionSignals('FIXME later')).toBe(true);
|
|
9
|
-
expect(hasOpenQuestionSignals('This is to be decided by the team')).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('detects placeholders', () => {
|
|
13
|
-
expect(hasOpenQuestionSignals('Region: ???')).toBe(true);
|
|
14
|
-
expect(hasOpenQuestionSignals('Contact: <add here>')).toBe(true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('detects unanswered question lines', () => {
|
|
18
|
-
expect(hasOpenQuestionSignals('# Notes\nWho owns the billing service?\n')).toBe(true);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('ignores questions inside fenced code blocks', () => {
|
|
22
|
-
expect(hasOpenQuestionSignals('```\nis this ok?\n```\nAll settled.')).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('returns false for clean docs', () => {
|
|
26
|
-
expect(hasOpenQuestionSignals('# Runbook\nRestart the service with systemctl.')).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('does not match marker substrings inside words', () => {
|
|
30
|
-
expect(hasOpenQuestionSignals('The mastodon population is stable.')).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('mapLimit', () => {
|
|
35
|
-
it('preserves order and maps all items', async () => {
|
|
36
|
-
const res = await mapLimit([1, 2, 3, 4, 5], 2, async (n) => n * 10);
|
|
37
|
-
expect(res.map((r) => (r.ok ? r.value : -1))).toEqual([10, 20, 30, 40, 50]);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('captures per-item errors without aborting the batch', async () => {
|
|
41
|
-
const res = await mapLimit([1, 2, 3], 2, async (n) => {
|
|
42
|
-
if (n === 2) throw new Error('boom');
|
|
43
|
-
return n;
|
|
44
|
-
});
|
|
45
|
-
expect(res[0]).toEqual({ ok: true, value: 1 });
|
|
46
|
-
expect(res[1].ok).toBe(false);
|
|
47
|
-
expect(res[2]).toEqual({ ok: true, value: 3 });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('respects the concurrency limit', async () => {
|
|
51
|
-
let active = 0;
|
|
52
|
-
let peak = 0;
|
|
53
|
-
await mapLimit([1, 2, 3, 4, 5, 6], 2, async () => {
|
|
54
|
-
active++;
|
|
55
|
-
peak = Math.max(peak, active);
|
|
56
|
-
await new Promise((r) => setTimeout(r, 5));
|
|
57
|
-
active--;
|
|
58
|
-
});
|
|
59
|
-
expect(peak).toBeLessThanOrEqual(2);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('handles empty input', async () => {
|
|
63
|
-
expect(await mapLimit([], 4, async () => 1)).toEqual([]);
|
|
64
|
-
});
|
|
65
|
-
});
|
package/test/slug.test.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { parseGithubSlug } from '../src/core/slug';
|
|
3
|
-
|
|
4
|
-
describe('parseGithubSlug', () => {
|
|
5
|
-
it('parses https remotes', () => {
|
|
6
|
-
expect(parseGithubSlug('https://github.com/ujjavala/docgrity-vscode.git')).toBe(
|
|
7
|
-
'ujjavala/docgrity-vscode'
|
|
8
|
-
);
|
|
9
|
-
expect(parseGithubSlug('https://github.com/ujjavala/docgrity-vscode')).toBe(
|
|
10
|
-
'ujjavala/docgrity-vscode'
|
|
11
|
-
);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('parses ssh remotes', () => {
|
|
15
|
-
expect(parseGithubSlug('git@github.com:owner/repo.git')).toBe('owner/repo');
|
|
16
|
-
expect(parseGithubSlug('ssh://git@github.com/owner/repo.git')).toBe('owner/repo');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('keeps dots in repo names', () => {
|
|
20
|
-
expect(parseGithubSlug('git@github.com:owner/my.repo.name.git')).toBe('owner/my.repo.name');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('handles trailing slash and whitespace', () => {
|
|
24
|
-
expect(parseGithubSlug(' https://github.com/owner/repo/ \n')).toBe('owner/repo');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('returns undefined for non-GitHub remotes', () => {
|
|
28
|
-
expect(parseGithubSlug('https://gitlab.com/owner/repo.git')).toBeUndefined();
|
|
29
|
-
expect(parseGithubSlug('')).toBeUndefined();
|
|
30
|
-
});
|
|
31
|
-
});
|
package/test/verify.test.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { verifyExcerpts } from '../src/core/verify';
|
|
3
|
-
|
|
4
|
-
const docA = 'The deploy pipeline runs on every merge to main.\nReleases are weekly.';
|
|
5
|
-
const docB = 'Releases happen monthly after QA sign-off.';
|
|
6
|
-
|
|
7
|
-
describe('verifyExcerpts', () => {
|
|
8
|
-
it('accepts excerpts present verbatim', () => {
|
|
9
|
-
expect(verifyExcerpts([{ excerpt: 'Releases are weekly.' }], [docA, docB])).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('is whitespace-insensitive', () => {
|
|
13
|
-
expect(
|
|
14
|
-
verifyExcerpts([{ excerpt: 'deploy pipeline runs on every merge' }], [docA])
|
|
15
|
-
).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('is case-insensitive', () => {
|
|
19
|
-
expect(verifyExcerpts([{ excerpt: 'RELEASES HAPPEN MONTHLY' }], [docB])).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('rejects hallucinated excerpts', () => {
|
|
23
|
-
expect(verifyExcerpts([{ excerpt: 'Releases are daily.' }], [docA, docB])).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('rejects when any excerpt fails (all-or-nothing)', () => {
|
|
27
|
-
expect(
|
|
28
|
-
verifyExcerpts(
|
|
29
|
-
[{ excerpt: 'Releases are weekly.' }, { excerpt: 'invented claim' }],
|
|
30
|
-
[docA, docB]
|
|
31
|
-
)
|
|
32
|
-
).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('rejects empty excerpts', () => {
|
|
36
|
-
expect(verifyExcerpts([{ excerpt: ' ' }], [docA])).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('accepts an empty excerpt list (nothing to verify)', () => {
|
|
40
|
-
expect(verifyExcerpts([], [docA])).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('only compares the first 200 normalised chars of long excerpts', () => {
|
|
44
|
-
const long = 'x'.repeat(300);
|
|
45
|
-
expect(verifyExcerpts([{ excerpt: long }], [`prefix ${'x'.repeat(200)} suffix`])).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"module": "commonjs",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"lib": ["ES2022"],
|
|
6
|
-
"outDir": "out",
|
|
7
|
-
"rootDir": "src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"sourceMap": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"types": ["node"],
|
|
12
|
-
"skipLibCheck": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src"]
|
|
15
|
-
}
|
package/vitest.config.mts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vitest/config';
|
|
2
|
-
|
|
3
|
-
// action/ is a zero-dependency package tested with node:test (`cd action && npm test`);
|
|
4
|
-
// vitest covers the extension sources only.
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
test: {
|
|
7
|
-
exclude: ['**/node_modules/**', 'action/**', 'out/**'],
|
|
8
|
-
},
|
|
9
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|