@mfittko/repo-wiki 0.2.1
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/.llmwiki/schema.md +107 -0
- package/AGENTS.md +42 -0
- package/CHANGELOG.md +91 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/bin/repo-wiki.d.ts +2 -0
- package/dist/bin/repo-wiki.js +7 -0
- package/dist/bin/repo-wiki.js.map +1 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +404 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/compiler.d.ts +55 -0
- package/dist/src/compiler.js +2046 -0
- package/dist/src/compiler.js.map +1 -0
- package/dist/src/config.d.ts +63 -0
- package/dist/src/config.js +86 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-assembler.d.ts +68 -0
- package/dist/src/context-assembler.js +378 -0
- package/dist/src/context-assembler.js.map +1 -0
- package/dist/src/data-model-signals.d.ts +1 -0
- package/dist/src/data-model-signals.js +13 -0
- package/dist/src/data-model-signals.js.map +1 -0
- package/dist/src/docs-ingestor.d.ts +138 -0
- package/dist/src/docs-ingestor.js +844 -0
- package/dist/src/docs-ingestor.js.map +1 -0
- package/dist/src/docs-linter.d.ts +14 -0
- package/dist/src/docs-linter.js +164 -0
- package/dist/src/docs-linter.js.map +1 -0
- package/dist/src/docs-validation.d.ts +36 -0
- package/dist/src/docs-validation.js +297 -0
- package/dist/src/docs-validation.js.map +1 -0
- package/dist/src/extractors.d.ts +50 -0
- package/dist/src/extractors.js +2275 -0
- package/dist/src/extractors.js.map +1 -0
- package/dist/src/frontmatter.d.ts +46 -0
- package/dist/src/frontmatter.js +377 -0
- package/dist/src/frontmatter.js.map +1 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.js +18 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/init.d.ts +12 -0
- package/dist/src/init.js +121 -0
- package/dist/src/init.js.map +1 -0
- package/dist/src/language.d.ts +2 -0
- package/dist/src/language.js +62 -0
- package/dist/src/language.js.map +1 -0
- package/dist/src/linter.d.ts +33 -0
- package/dist/src/linter.js +398 -0
- package/dist/src/linter.js.map +1 -0
- package/dist/src/llm-provider.d.ts +267 -0
- package/dist/src/llm-provider.js +474 -0
- package/dist/src/llm-provider.js.map +1 -0
- package/dist/src/page-ownership.d.ts +38 -0
- package/dist/src/page-ownership.js +96 -0
- package/dist/src/page-ownership.js.map +1 -0
- package/dist/src/planner.d.ts +55 -0
- package/dist/src/planner.js +422 -0
- package/dist/src/planner.js.map +1 -0
- package/dist/src/prompts.d.ts +103 -0
- package/dist/src/prompts.js +344 -0
- package/dist/src/prompts.js.map +1 -0
- package/dist/src/publisher.d.ts +68 -0
- package/dist/src/publisher.js +662 -0
- package/dist/src/publisher.js.map +1 -0
- package/dist/src/repository-analysis.d.ts +88 -0
- package/dist/src/repository-analysis.js +485 -0
- package/dist/src/repository-analysis.js.map +1 -0
- package/dist/src/scanner.d.ts +122 -0
- package/dist/src/scanner.js +309 -0
- package/dist/src/scanner.js.map +1 -0
- package/dist/src/search.d.ts +71 -0
- package/dist/src/search.js +410 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/secret-patterns.d.ts +3 -0
- package/dist/src/secret-patterns.js +14 -0
- package/dist/src/secret-patterns.js.map +1 -0
- package/dist/src/utils/args.d.ts +2 -0
- package/dist/src/utils/args.js +19 -0
- package/dist/src/utils/args.js.map +1 -0
- package/dist/src/utils/dotenv.d.ts +7 -0
- package/dist/src/utils/dotenv.js +73 -0
- package/dist/src/utils/dotenv.js.map +1 -0
- package/dist/src/utils/fs.d.ts +22 -0
- package/dist/src/utils/fs.js +83 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/src/utils/git.d.ts +13 -0
- package/dist/src/utils/git.js +39 -0
- package/dist/src/utils/git.js.map +1 -0
- package/dist/src/wiki-graph.d.ts +74 -0
- package/dist/src/wiki-graph.js +335 -0
- package/dist/src/wiki-graph.js.map +1 -0
- package/dist/src/wiki-patch.d.ts +152 -0
- package/dist/src/wiki-patch.js +489 -0
- package/dist/src/wiki-patch.js.map +1 -0
- package/dist/src/wiki-query.d.ts +63 -0
- package/dist/src/wiki-query.js +255 -0
- package/dist/src/wiki-query.js.map +1 -0
- package/dist/test/cli.test.d.ts +1 -0
- package/dist/test/cli.test.js +514 -0
- package/dist/test/cli.test.js.map +1 -0
- package/dist/test/compiler-eval.test.d.ts +1 -0
- package/dist/test/compiler-eval.test.js +234 -0
- package/dist/test/compiler-eval.test.js.map +1 -0
- package/dist/test/compiler.test.d.ts +1 -0
- package/dist/test/compiler.test.js +2537 -0
- package/dist/test/compiler.test.js.map +1 -0
- package/dist/test/context-assembler.test.d.ts +1 -0
- package/dist/test/context-assembler.test.js +379 -0
- package/dist/test/context-assembler.test.js.map +1 -0
- package/dist/test/docs-linter.test.d.ts +1 -0
- package/dist/test/docs-linter.test.js +900 -0
- package/dist/test/docs-linter.test.js.map +1 -0
- package/dist/test/dotenv.test.d.ts +1 -0
- package/dist/test/dotenv.test.js +77 -0
- package/dist/test/dotenv.test.js.map +1 -0
- package/dist/test/extractors-go.test.d.ts +1 -0
- package/dist/test/extractors-go.test.js +393 -0
- package/dist/test/extractors-go.test.js.map +1 -0
- package/dist/test/extractors-rust.test.d.ts +1 -0
- package/dist/test/extractors-rust.test.js +219 -0
- package/dist/test/extractors-rust.test.js.map +1 -0
- package/dist/test/extractors-utils.test.d.ts +1 -0
- package/dist/test/extractors-utils.test.js +786 -0
- package/dist/test/extractors-utils.test.js.map +1 -0
- package/dist/test/fixtures/compiler-e2e/basic-node-service/repo/infra/deploy.d.ts +1 -0
- package/dist/test/fixtures/compiler-e2e/basic-node-service/repo/infra/deploy.js +4 -0
- package/dist/test/fixtures/compiler-e2e/basic-node-service/repo/infra/deploy.js.map +1 -0
- package/dist/test/frontmatter.test.d.ts +1 -0
- package/dist/test/frontmatter.test.js +287 -0
- package/dist/test/frontmatter.test.js.map +1 -0
- package/dist/test/init-planner.test.d.ts +1 -0
- package/dist/test/init-planner.test.js +688 -0
- package/dist/test/init-planner.test.js.map +1 -0
- package/dist/test/linter.test.d.ts +1 -0
- package/dist/test/linter.test.js +426 -0
- package/dist/test/linter.test.js.map +1 -0
- package/dist/test/llm-provider.test.d.ts +1 -0
- package/dist/test/llm-provider.test.js +783 -0
- package/dist/test/llm-provider.test.js.map +1 -0
- package/dist/test/page-ownership.test.d.ts +1 -0
- package/dist/test/page-ownership.test.js +247 -0
- package/dist/test/page-ownership.test.js.map +1 -0
- package/dist/test/publisher.test.d.ts +1 -0
- package/dist/test/publisher.test.js +1297 -0
- package/dist/test/publisher.test.js.map +1 -0
- package/dist/test/repository-analysis.test.d.ts +1 -0
- package/dist/test/repository-analysis.test.js +182 -0
- package/dist/test/repository-analysis.test.js.map +1 -0
- package/dist/test/run-compiled-tests.d.ts +1 -0
- package/dist/test/run-compiled-tests.js +48 -0
- package/dist/test/run-compiled-tests.js.map +1 -0
- package/dist/test/scanner.test.d.ts +1 -0
- package/dist/test/scanner.test.js +551 -0
- package/dist/test/scanner.test.js.map +1 -0
- package/dist/test/search.test.d.ts +1 -0
- package/dist/test/search.test.js +92 -0
- package/dist/test/search.test.js.map +1 -0
- package/dist/test/update-changelog.test.d.ts +1 -0
- package/dist/test/update-changelog.test.js +125 -0
- package/dist/test/update-changelog.test.js.map +1 -0
- package/dist/test/wiki-graph.test.d.ts +1 -0
- package/dist/test/wiki-graph.test.js +164 -0
- package/dist/test/wiki-graph.test.js.map +1 -0
- package/dist/test/wiki-patch.test.d.ts +1 -0
- package/dist/test/wiki-patch.test.js +610 -0
- package/dist/test/wiki-patch.test.js.map +1 -0
- package/dist/test/wiki-query.test.d.ts +1 -0
- package/dist/test/wiki-query.test.js +163 -0
- package/dist/test/wiki-query.test.js.map +1 -0
- package/docs/PLAN.md +993 -0
- package/docs/WHY.md +61 -0
- package/docs/plans/agent-integration.md +85 -0
- package/docs/plans/ci-publishing.md +111 -0
- package/docs/plans/doc-validation.md +92 -0
- package/docs/plans/github-action.md +113 -0
- package/docs/plans/incremental-mode.md +98 -0
- package/docs/plans/karpathy-llm-wiki-alignment.md +84 -0
- package/docs/plans/llm-compiler.md +160 -0
- package/docs/plans/production-scanner.md +104 -0
- package/docs/plans/query-and-file-back.md +103 -0
- package/docs/plans/search-index.md +118 -0
- package/docs/plans/trust-hardening.md +74 -0
- package/docs/plans/wiki-graph.md +183 -0
- package/docs/plans/wiki-health.md +76 -0
- package/package.json +83 -0
- package/prompts/compiler.md +16 -0
- package/prompts/lint.md +18 -0
- package/prompts/page-templates.md +25 -0
- package/skills/repo-wiki-cli/SKILL.md +139 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { WikiPatchError, validateWikiPatch, parseWikiPatch, synthesizeWikiPage, } from '../src/wiki-patch.js';
|
|
4
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
5
|
+
/** Minimal valid patch that passes all error-level gates. */
|
|
6
|
+
function validPatch(overrides = {}) {
|
|
7
|
+
const fm = [
|
|
8
|
+
'---',
|
|
9
|
+
`source_commit: ${JSON.stringify(overrides.source_commit ?? 'abc123')}`,
|
|
10
|
+
`kind: ${JSON.stringify(overrides.kind ?? 'module')}`,
|
|
11
|
+
`compiled_at: ${JSON.stringify(overrides.compiled_at ?? '2026-01-01T00:00:00.000Z')}`,
|
|
12
|
+
`source_paths: ${overrides.source_paths ?? '["src/example.ts"]'}`,
|
|
13
|
+
'---',
|
|
14
|
+
].join('\n');
|
|
15
|
+
const body = overrides.body ?? '\n# Example\n\nSome content.\n';
|
|
16
|
+
return `${fm}\n${body}`;
|
|
17
|
+
}
|
|
18
|
+
/** Mock provider that returns fixed content. */
|
|
19
|
+
function fixedProvider(content) {
|
|
20
|
+
return {
|
|
21
|
+
name: 'fixed-mock',
|
|
22
|
+
async complete(_request) {
|
|
23
|
+
return { content, provider: 'fixed-mock' };
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Mock provider that returns a sequence of responses in order. */
|
|
28
|
+
function sequenceProvider(responses) {
|
|
29
|
+
let index = 0;
|
|
30
|
+
return {
|
|
31
|
+
name: 'sequence-mock',
|
|
32
|
+
async complete(_request) {
|
|
33
|
+
const content = responses[Math.min(index, responses.length - 1)];
|
|
34
|
+
index++;
|
|
35
|
+
return { content, provider: 'sequence-mock' };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function makeRequest(overrides = {}) {
|
|
40
|
+
return {
|
|
41
|
+
archetype: 'module',
|
|
42
|
+
pageName: 'Module-Auth',
|
|
43
|
+
pageTitle: 'Auth',
|
|
44
|
+
systemPrompt: 'System.',
|
|
45
|
+
userPrompt: 'User.',
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function codes(issues) {
|
|
50
|
+
return issues.map((i) => i.code);
|
|
51
|
+
}
|
|
52
|
+
// ── validateWikiPatch ──────────────────────────────────────────────────────
|
|
53
|
+
test('validateWikiPatch returns no issues for a valid patch', () => {
|
|
54
|
+
const issues = validateWikiPatch(validPatch(), 'Module-Auth');
|
|
55
|
+
assert.deepEqual(issues, []);
|
|
56
|
+
});
|
|
57
|
+
test('validateWikiPatch strips only a surrounding markdown fence when inner content starts with frontmatter', () => {
|
|
58
|
+
const issues = validateWikiPatch(`\`\`\`markdown\n${validPatch()}\n\`\`\``, 'Module-Auth');
|
|
59
|
+
assert.deepEqual(issues, []);
|
|
60
|
+
});
|
|
61
|
+
test('validateWikiPatch strips surrounding markdown fence without a final inner newline', () => {
|
|
62
|
+
const issues = validateWikiPatch(`\`\`\`markdown\n${validPatch().trimEnd()}\`\`\``, 'Module-Auth');
|
|
63
|
+
assert.deepEqual(issues, []);
|
|
64
|
+
});
|
|
65
|
+
test('validateWikiPatch still rejects fenced content when inner content lacks frontmatter', () => {
|
|
66
|
+
const issues = validateWikiPatch('```markdown\n# No frontmatter\n```', 'Module-Auth');
|
|
67
|
+
assert.ok(codes(issues).includes('missing-frontmatter'));
|
|
68
|
+
});
|
|
69
|
+
test('validateWikiPatch returns error for empty content', () => {
|
|
70
|
+
const issues = validateWikiPatch('', 'Module-Auth');
|
|
71
|
+
assert.ok(codes(issues).includes('empty-content'), 'should report empty-content');
|
|
72
|
+
assert.ok(issues.find((i) => i.code === 'empty-content')?.level === 'error', 'empty-content should be error level');
|
|
73
|
+
});
|
|
74
|
+
test('validateWikiPatch returns error for whitespace-only content', () => {
|
|
75
|
+
const issues = validateWikiPatch(' \n ', 'Module-Auth');
|
|
76
|
+
assert.ok(codes(issues).includes('empty-content'));
|
|
77
|
+
});
|
|
78
|
+
test('validateWikiPatch returns error when frontmatter is missing', () => {
|
|
79
|
+
const issues = validateWikiPatch('# Just a heading\n\nNo frontmatter.', 'Module-Auth');
|
|
80
|
+
assert.ok(codes(issues).includes('missing-frontmatter'));
|
|
81
|
+
assert.equal(issues.find((i) => i.code === 'missing-frontmatter')?.level, 'error');
|
|
82
|
+
});
|
|
83
|
+
test('validateWikiPatch returns error for unclosed frontmatter', () => {
|
|
84
|
+
const content = '---\nsource_commit: "abc"\nkind: "module"\n# no closing ---';
|
|
85
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
86
|
+
assert.ok(codes(issues).includes('missing-frontmatter'));
|
|
87
|
+
});
|
|
88
|
+
test('validateWikiPatch rejects malformed closing frontmatter delimiter', () => {
|
|
89
|
+
const content = '---\nsource_commit: "abc"\nkind: "module"\n---not-a-delimiter\n# Body\n';
|
|
90
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
91
|
+
assert.ok(codes(issues).includes('missing-frontmatter'));
|
|
92
|
+
});
|
|
93
|
+
test('validateWikiPatch returns error when source_commit is missing', () => {
|
|
94
|
+
const content = '---\nkind: "module"\ncompiled_at: "2026-01-01T00:00:00.000Z"\nsource_paths: []\n---\n\n# Body\n';
|
|
95
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
96
|
+
assert.ok(codes(issues).includes('missing-source-commit'));
|
|
97
|
+
assert.equal(issues.find((i) => i.code === 'missing-source-commit')?.level, 'error');
|
|
98
|
+
});
|
|
99
|
+
test('validateWikiPatch returns error when source_commit is blank', () => {
|
|
100
|
+
const content = '---\nsource_commit: ""\nkind: "module"\ncompiled_at: "2026-01-01T00:00:00.000Z"\nsource_paths: []\n---\n\n# Body\n';
|
|
101
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
102
|
+
assert.ok(codes(issues).includes('missing-source-commit'));
|
|
103
|
+
});
|
|
104
|
+
test('validateWikiPatch rejects non-string YAML source_commit scalars', () => {
|
|
105
|
+
for (const value of ['123', 'true', 'null']) {
|
|
106
|
+
const content = `---\nsource_commit: ${value}\nkind: "module"\ncompiled_at: "2026-01-01T00:00:00.000Z"\nsource_paths: []\n---\n\n# Body\n`;
|
|
107
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
108
|
+
assert.ok(codes(issues).includes('missing-source-commit'), `should reject source_commit: ${value}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
test('validateWikiPatch returns error when kind is missing', () => {
|
|
112
|
+
const content = '---\nsource_commit: "abc123"\ncompiled_at: "2026-01-01T00:00:00.000Z"\nsource_paths: []\n---\n\n# Body\n';
|
|
113
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
114
|
+
assert.ok(codes(issues).includes('missing-kind'));
|
|
115
|
+
assert.equal(issues.find((i) => i.code === 'missing-kind')?.level, 'error');
|
|
116
|
+
});
|
|
117
|
+
test('validateWikiPatch rejects non-string YAML kind scalars', () => {
|
|
118
|
+
for (const value of ['123', 'true', 'null']) {
|
|
119
|
+
const content = `---\nsource_commit: "abc123"\nkind: ${value}\ncompiled_at: "2026-01-01T00:00:00.000Z"\nsource_paths: []\n---\n\n# Body\n`;
|
|
120
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
121
|
+
assert.ok(codes(issues).includes('missing-kind'), `should reject kind: ${value}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
test('validateWikiPatch returns error when compiled_at is missing', () => {
|
|
125
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\nsource_paths: []\n---\n\n# Body\n';
|
|
126
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
127
|
+
assert.ok(codes(issues).includes('missing-compiled-at'));
|
|
128
|
+
assert.equal(issues.find((i) => i.code === 'missing-compiled-at')?.level, 'error');
|
|
129
|
+
});
|
|
130
|
+
test('validateWikiPatch returns error when compiled_at is blank', () => {
|
|
131
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\ncompiled_at: ""\nsource_paths: []\n---\n\n# Body\n';
|
|
132
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
133
|
+
assert.ok(codes(issues).includes('missing-compiled-at'));
|
|
134
|
+
});
|
|
135
|
+
test('validateWikiPatch rejects non-string YAML compiled_at scalars', () => {
|
|
136
|
+
for (const value of ['123', 'true', 'null']) {
|
|
137
|
+
const content = `---\nsource_commit: "abc123"\nkind: "module"\ncompiled_at: ${value}\nsource_paths: []\n---\n\n# Body\n`;
|
|
138
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
139
|
+
assert.ok(codes(issues).includes('missing-compiled-at'), `should reject compiled_at: ${value}`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
test('validateWikiPatch returns error when body is empty after frontmatter', () => {
|
|
143
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\nsource_paths: []\n---\n';
|
|
144
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
145
|
+
assert.ok(codes(issues).includes('empty-body'));
|
|
146
|
+
assert.equal(issues.find((i) => i.code === 'empty-body')?.level, 'error');
|
|
147
|
+
});
|
|
148
|
+
test('validateWikiPatch returns error when body is whitespace-only', () => {
|
|
149
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\nsource_paths: []\n---\n \n';
|
|
150
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
151
|
+
assert.ok(codes(issues).includes('empty-body'));
|
|
152
|
+
});
|
|
153
|
+
test('validateWikiPatch returns warning when source_paths is missing', () => {
|
|
154
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\n---\n\n# Body\n';
|
|
155
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
156
|
+
assert.ok(codes(issues).includes('missing-source-paths'));
|
|
157
|
+
assert.equal(issues.find((i) => i.code === 'missing-source-paths')?.level, 'warning');
|
|
158
|
+
});
|
|
159
|
+
test('validateWikiPatch accepts empty source_paths array', () => {
|
|
160
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\nsource_paths: []\n---\n\n# Body\n';
|
|
161
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
162
|
+
assert.ok(!codes(issues).includes('missing-source-paths'), 'empty array is acceptable');
|
|
163
|
+
assert.ok(!codes(issues).includes('empty-content'));
|
|
164
|
+
assert.ok(!codes(issues).includes('missing-source-commit'));
|
|
165
|
+
});
|
|
166
|
+
test('validateWikiPatch returns error when source_paths is present but not an array', () => {
|
|
167
|
+
const content = validPatch({ source_paths: '"src/a.ts"' });
|
|
168
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
169
|
+
assert.ok(codes(issues).includes('invalid-source-paths'));
|
|
170
|
+
assert.equal(issues.find((i) => i.code === 'invalid-source-paths')?.level, 'error');
|
|
171
|
+
assert.ok(!codes(issues).includes('missing-source-paths'));
|
|
172
|
+
});
|
|
173
|
+
test('validateWikiPatch returns error when source_paths contains non-strings', () => {
|
|
174
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\nsource_paths: ["src/a.ts", 123, true]\n---\n\n# Body\n';
|
|
175
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
176
|
+
assert.ok(codes(issues).includes('invalid-source-paths'));
|
|
177
|
+
assert.equal(issues.find((i) => i.code === 'invalid-source-paths')?.level, 'error');
|
|
178
|
+
});
|
|
179
|
+
test('validateWikiPatch returns error when source_paths contains blank strings', () => {
|
|
180
|
+
const content = '---\nsource_commit: "abc123"\nkind: "module"\nsource_paths: ["src/a.ts", " "]\n---\n\n# Body\n';
|
|
181
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
182
|
+
assert.ok(codes(issues).includes('invalid-source-paths'));
|
|
183
|
+
});
|
|
184
|
+
test('validateWikiPatch returns error for AWS access key pattern', () => {
|
|
185
|
+
const content = validPatch({ body: '\n# Body\n\nAKIAIOSFODNN7EXAMPLE\n' });
|
|
186
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
187
|
+
assert.ok(codes(issues).includes('secret-like-content'));
|
|
188
|
+
assert.equal(issues.find((i) => i.code === 'secret-like-content')?.level, 'error');
|
|
189
|
+
});
|
|
190
|
+
test('validateWikiPatch returns error for GitHub token pattern', () => {
|
|
191
|
+
const syntheticToken = 'ghp_' + 'aAbBcCdDeEfFgGhHiIjJkKlLmMnNoO';
|
|
192
|
+
const content = validPatch({ body: `\n# Body\n\n${syntheticToken}\n` });
|
|
193
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
194
|
+
assert.ok(codes(issues).includes('secret-like-content'));
|
|
195
|
+
});
|
|
196
|
+
test('validateWikiPatch returns error for bearer token pattern', () => {
|
|
197
|
+
const content = validPatch({ body: '\n# Body\n\nAuthorization: Bearer mysecrettoken123\n' });
|
|
198
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
199
|
+
assert.ok(codes(issues).includes('secret-like-content'));
|
|
200
|
+
});
|
|
201
|
+
test('validateWikiPatch returns error for key=value credential pattern', () => {
|
|
202
|
+
const content = validPatch({ body: '\n# Body\n\napi_key=super-secret-value-here\n' });
|
|
203
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
204
|
+
assert.ok(codes(issues).includes('secret-like-content'));
|
|
205
|
+
});
|
|
206
|
+
test('validateWikiPatch includes pageName in issue messages', () => {
|
|
207
|
+
const issues = validateWikiPatch('', 'My-Special-Page');
|
|
208
|
+
assert.ok(issues.every((i) => i.message.includes('My-Special-Page')));
|
|
209
|
+
});
|
|
210
|
+
test('validateWikiPatch handles bare (non-JSON-quoted) scalar values', () => {
|
|
211
|
+
const content = '---\nsource_commit: abc123\nkind: module\ncompiled_at: 2026-01-01T00:00:00.000Z\nsource_paths: []\n---\n\n# Body\n';
|
|
212
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
213
|
+
assert.deepEqual(issues, []);
|
|
214
|
+
});
|
|
215
|
+
test('validateWikiPatch handles YAML block sequences for source_paths', () => {
|
|
216
|
+
const content = [
|
|
217
|
+
'---',
|
|
218
|
+
'source_commit: "abc123"',
|
|
219
|
+
'kind: "module"',
|
|
220
|
+
'compiled_at: "2026-01-01T00:00:00.000Z"',
|
|
221
|
+
'source_paths:',
|
|
222
|
+
' - "src/a.ts"',
|
|
223
|
+
' - "src/b.ts"',
|
|
224
|
+
'---',
|
|
225
|
+
'',
|
|
226
|
+
'# Body',
|
|
227
|
+
'',
|
|
228
|
+
'Some content.',
|
|
229
|
+
'',
|
|
230
|
+
].join('\n');
|
|
231
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
232
|
+
assert.deepEqual(issues, []);
|
|
233
|
+
});
|
|
234
|
+
test('validateWikiPatch rejects non-string YAML block sequence source_paths entries', () => {
|
|
235
|
+
for (const value of ['123', 'true', 'null']) {
|
|
236
|
+
const content = [
|
|
237
|
+
'---',
|
|
238
|
+
'source_commit: "abc123"',
|
|
239
|
+
'kind: "module"',
|
|
240
|
+
'source_paths:',
|
|
241
|
+
' - "src/a.ts"',
|
|
242
|
+
` - ${value}`,
|
|
243
|
+
'---',
|
|
244
|
+
'',
|
|
245
|
+
'# Body',
|
|
246
|
+
'',
|
|
247
|
+
].join('\n');
|
|
248
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
249
|
+
assert.ok(codes(issues).includes('invalid-source-paths'), `should reject source_paths entry: ${value}`);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
test('validateWikiPatch handles BOM-prefixed content', () => {
|
|
253
|
+
const content = '\uFEFF' + validPatch();
|
|
254
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
255
|
+
assert.deepEqual(issues, []);
|
|
256
|
+
});
|
|
257
|
+
test('validateWikiPatch handles CRLF line endings', () => {
|
|
258
|
+
const content = validPatch().replace(/\n/g, '\r\n');
|
|
259
|
+
const issues = validateWikiPatch(content, 'Module-Auth');
|
|
260
|
+
assert.deepEqual(issues, []);
|
|
261
|
+
});
|
|
262
|
+
// ── parseWikiPatch ─────────────────────────────────────────────────────────
|
|
263
|
+
test('parseWikiPatch returns a WikiPatch for valid content', () => {
|
|
264
|
+
const content = validPatch();
|
|
265
|
+
const patch = parseWikiPatch(content, 'Module-Auth');
|
|
266
|
+
assert.equal(patch.pageName, 'Module-Auth');
|
|
267
|
+
assert.equal(patch.frontmatter.source_commit, 'abc123');
|
|
268
|
+
assert.equal(patch.frontmatter.kind, 'module');
|
|
269
|
+
assert.deepEqual(patch.frontmatter.source_paths, ['src/example.ts']);
|
|
270
|
+
assert.ok(patch.body.trim().length > 0, 'body must be non-empty');
|
|
271
|
+
assert.ok(patch.content.startsWith('---'), 'content should include frontmatter');
|
|
272
|
+
});
|
|
273
|
+
test('parseWikiPatch throws WikiPatchError for invalid content', () => {
|
|
274
|
+
assert.throws(() => parseWikiPatch('# No frontmatter', 'Module-Auth'), WikiPatchError);
|
|
275
|
+
});
|
|
276
|
+
test('parseWikiPatch error contains all issues', () => {
|
|
277
|
+
// Missing both source_commit and kind
|
|
278
|
+
const content = '---\nsource_paths: []\n---\n\n# Body\n';
|
|
279
|
+
try {
|
|
280
|
+
parseWikiPatch(content, 'Module-Auth');
|
|
281
|
+
assert.fail('expected WikiPatchError to be thrown');
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
assert.ok(err instanceof WikiPatchError);
|
|
285
|
+
const issueCodes = codes(err.issues);
|
|
286
|
+
assert.ok(issueCodes.includes('missing-source-commit'));
|
|
287
|
+
assert.ok(issueCodes.includes('missing-kind'));
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
test('parseWikiPatch error exposes pageName', () => {
|
|
291
|
+
try {
|
|
292
|
+
parseWikiPatch('', 'Specific-Page');
|
|
293
|
+
assert.fail('expected WikiPatchError to be thrown');
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
assert.ok(err instanceof WikiPatchError);
|
|
297
|
+
assert.equal(err.pageName, 'Specific-Page');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
test('parseWikiPatch error message includes error count', () => {
|
|
301
|
+
try {
|
|
302
|
+
parseWikiPatch('', 'Module-Auth');
|
|
303
|
+
assert.fail('expected WikiPatchError');
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
assert.ok(err instanceof WikiPatchError);
|
|
307
|
+
assert.match(err.message, /1 error/);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
test('parseWikiPatch parses source_paths as array', () => {
|
|
311
|
+
const content = [
|
|
312
|
+
'---',
|
|
313
|
+
'source_commit: "abc123"',
|
|
314
|
+
'kind: "module"',
|
|
315
|
+
'compiled_at: "2026-01-01T00:00:00.000Z"',
|
|
316
|
+
'source_paths:',
|
|
317
|
+
' - "src/a.ts"',
|
|
318
|
+
' - "src/b.ts"',
|
|
319
|
+
'---',
|
|
320
|
+
'',
|
|
321
|
+
'# Module',
|
|
322
|
+
'',
|
|
323
|
+
'Content here.',
|
|
324
|
+
].join('\n');
|
|
325
|
+
const patch = parseWikiPatch(content, 'Module-Auth');
|
|
326
|
+
assert.deepEqual(patch.frontmatter.source_paths, ['src/a.ts', 'src/b.ts']);
|
|
327
|
+
});
|
|
328
|
+
test('parseWikiPatch passes through extra frontmatter fields', () => {
|
|
329
|
+
const content = [
|
|
330
|
+
'---',
|
|
331
|
+
'source_commit: "abc123"',
|
|
332
|
+
'kind: "module"',
|
|
333
|
+
'source_paths: []',
|
|
334
|
+
'compiled_at: "2026-01-01T00:00:00.000Z"',
|
|
335
|
+
'page_state: "generated"',
|
|
336
|
+
'---',
|
|
337
|
+
'',
|
|
338
|
+
'# Module',
|
|
339
|
+
'',
|
|
340
|
+
'Content.',
|
|
341
|
+
].join('\n');
|
|
342
|
+
const patch = parseWikiPatch(content, 'Module-Auth');
|
|
343
|
+
assert.equal(patch.frontmatter.compiled_at, '2026-01-01T00:00:00.000Z');
|
|
344
|
+
assert.equal(patch.frontmatter.page_state, 'generated');
|
|
345
|
+
});
|
|
346
|
+
test('parseWikiPatch body does not include frontmatter', () => {
|
|
347
|
+
const content = validPatch({ body: '\n# Title\n\nBody content.\n' });
|
|
348
|
+
const patch = parseWikiPatch(content, 'Module-Auth');
|
|
349
|
+
assert.ok(!patch.body.includes('source_commit'), 'body should not include frontmatter fields');
|
|
350
|
+
assert.ok(patch.body.includes('# Title'));
|
|
351
|
+
});
|
|
352
|
+
test('parseWikiPatch includes warnings in error issues but still throws', () => {
|
|
353
|
+
// Has secret + missing source_paths warning
|
|
354
|
+
const content = [
|
|
355
|
+
'---',
|
|
356
|
+
'source_commit: "abc123"',
|
|
357
|
+
'kind: "module"',
|
|
358
|
+
// no source_paths → warning
|
|
359
|
+
'---',
|
|
360
|
+
'',
|
|
361
|
+
'# Body',
|
|
362
|
+
'',
|
|
363
|
+
'AKIAIOSFODNN7EXAMPLE', // → error
|
|
364
|
+
].join('\n');
|
|
365
|
+
try {
|
|
366
|
+
parseWikiPatch(content, 'Module-Auth');
|
|
367
|
+
assert.fail('expected WikiPatchError');
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
assert.ok(err instanceof WikiPatchError);
|
|
371
|
+
const issueCodes = codes(err.issues);
|
|
372
|
+
assert.ok(issueCodes.includes('secret-like-content'));
|
|
373
|
+
assert.ok(issueCodes.includes('missing-source-paths'));
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
test('parseWikiPatch accepts mock provider output format', () => {
|
|
377
|
+
// This mirrors what MockLLMProvider.buildMockContent produces
|
|
378
|
+
const content = [
|
|
379
|
+
'---',
|
|
380
|
+
'kind: "module"',
|
|
381
|
+
'page_name: "Module-Auth"',
|
|
382
|
+
'compiled_at: "mock"',
|
|
383
|
+
'source_commit: "mock"',
|
|
384
|
+
'source_paths: []',
|
|
385
|
+
'---',
|
|
386
|
+
'',
|
|
387
|
+
'# Auth',
|
|
388
|
+
'',
|
|
389
|
+
'> Generated by the mock LLM provider (deterministic, no network).',
|
|
390
|
+
'',
|
|
391
|
+
'**Archetype:** module',
|
|
392
|
+
'',
|
|
393
|
+
'<!-- HUMAN_NOTES_START -->',
|
|
394
|
+
'<!-- HUMAN_NOTES_END -->',
|
|
395
|
+
'',
|
|
396
|
+
].join('\n');
|
|
397
|
+
const patch = parseWikiPatch(content, 'Module-Auth');
|
|
398
|
+
assert.equal(patch.frontmatter.source_commit, 'mock');
|
|
399
|
+
assert.equal(patch.frontmatter.kind, 'module');
|
|
400
|
+
assert.ok(patch.body.includes('# Auth'));
|
|
401
|
+
});
|
|
402
|
+
// ── synthesizeWikiPage ─────────────────────────────────────────────────────
|
|
403
|
+
test('synthesizeWikiPage returns WikiPatch for valid provider output', async () => {
|
|
404
|
+
const provider = fixedProvider(validPatch());
|
|
405
|
+
const patch = await synthesizeWikiPage(provider, makeRequest());
|
|
406
|
+
assert.ok(patch instanceof Object);
|
|
407
|
+
assert.equal(patch.pageName, 'Module-Auth');
|
|
408
|
+
assert.equal(patch.frontmatter.kind, 'module');
|
|
409
|
+
});
|
|
410
|
+
test('synthesizeWikiPage throws WikiPatchError when provider returns invalid content', async () => {
|
|
411
|
+
const provider = fixedProvider('# Just markdown, no frontmatter');
|
|
412
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest()), WikiPatchError);
|
|
413
|
+
});
|
|
414
|
+
test('synthesizeWikiPage succeeds on second attempt with maxRetries=1', async () => {
|
|
415
|
+
const provider = sequenceProvider([
|
|
416
|
+
'# Invalid - no frontmatter', // attempt 0 → fails
|
|
417
|
+
validPatch(), // attempt 1 → succeeds
|
|
418
|
+
]);
|
|
419
|
+
const patch = await synthesizeWikiPage(provider, makeRequest(), { maxRetries: 1 });
|
|
420
|
+
assert.equal(patch.frontmatter.kind, 'module');
|
|
421
|
+
});
|
|
422
|
+
test('synthesizeWikiPage appends corrective validation feedback on retry without mutating original request', async () => {
|
|
423
|
+
const seenPrompts = [];
|
|
424
|
+
const request = makeRequest({ userPrompt: 'Original module prompt.' });
|
|
425
|
+
const provider = {
|
|
426
|
+
name: 'feedback-aware-mock',
|
|
427
|
+
async complete(nextRequest) {
|
|
428
|
+
seenPrompts.push(nextRequest.userPrompt);
|
|
429
|
+
if (nextRequest.userPrompt.includes('Previous response was rejected') && nextRequest.userPrompt.includes('missing-frontmatter')) {
|
|
430
|
+
return { content: validPatch(), provider: 'feedback-aware-mock' };
|
|
431
|
+
}
|
|
432
|
+
return { content: 'I can write that page.\n\n# Missing frontmatter', provider: 'feedback-aware-mock' };
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
const patch = await synthesizeWikiPage(provider, request, { maxRetries: 1 });
|
|
436
|
+
assert.equal(patch.frontmatter.kind, 'module');
|
|
437
|
+
assert.equal(request.userPrompt, 'Original module prompt.');
|
|
438
|
+
assert.equal(seenPrompts.length, 2);
|
|
439
|
+
assert.equal(seenPrompts[0], 'Original module prompt.');
|
|
440
|
+
assert.match(seenPrompts[1], /Previous response was rejected by repo-wiki structured patch validation/);
|
|
441
|
+
assert.match(seenPrompts[1], /error missing-frontmatter:/);
|
|
442
|
+
assert.match(seenPrompts[1], /Output only raw markdown/);
|
|
443
|
+
assert.match(seenPrompts[1], /first line must be exactly `---`/);
|
|
444
|
+
assert.match(seenPrompts[1], /Do not include any preamble, commentary, or fenced code block wrapper/);
|
|
445
|
+
assert.match(seenPrompts[1], /source_paths must be a non-empty array/);
|
|
446
|
+
});
|
|
447
|
+
test('synthesizeWikiPage throws after exhausting all corrective retries when output remains invalid', async () => {
|
|
448
|
+
let callCount = 0;
|
|
449
|
+
const provider = {
|
|
450
|
+
name: 'always-invalid-feedback-mock',
|
|
451
|
+
async complete(_request) {
|
|
452
|
+
callCount++;
|
|
453
|
+
return { content: 'Prose without frontmatter despite feedback.', provider: 'always-invalid-feedback-mock' };
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest(), { maxRetries: 1 }), (err) => err instanceof WikiPatchError && codes(err.issues).includes('missing-frontmatter'));
|
|
457
|
+
assert.equal(callCount, 2);
|
|
458
|
+
});
|
|
459
|
+
test('synthesizeWikiPage throws after exhausting all retries', async () => {
|
|
460
|
+
const provider = fixedProvider('# No frontmatter ever');
|
|
461
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest(), { maxRetries: 2 }), WikiPatchError);
|
|
462
|
+
});
|
|
463
|
+
test('synthesizeWikiPage uses last error when retries exhausted', async () => {
|
|
464
|
+
// All responses are invalid
|
|
465
|
+
const provider = fixedProvider('');
|
|
466
|
+
try {
|
|
467
|
+
await synthesizeWikiPage(provider, makeRequest(), { maxRetries: 1 });
|
|
468
|
+
assert.fail('expected WikiPatchError');
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
assert.ok(err instanceof WikiPatchError);
|
|
472
|
+
assert.ok(codes(err.issues).includes('empty-content'));
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
test('synthesizeWikiPage passes through LLMProviderError without retry', async () => {
|
|
476
|
+
const { LLMProviderError } = await import('../src/llm-provider.js');
|
|
477
|
+
const provider = {
|
|
478
|
+
name: 'failing',
|
|
479
|
+
async complete() {
|
|
480
|
+
throw new LLMProviderError('Network failure', 'failing', 'TIMEOUT', false);
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest(), { maxRetries: 2 }), (err) => err instanceof LLMProviderError);
|
|
484
|
+
});
|
|
485
|
+
test('synthesizeWikiPage defaults to maxRetries=0 when not specified', async () => {
|
|
486
|
+
let callCount = 0;
|
|
487
|
+
const provider = {
|
|
488
|
+
name: 'counting',
|
|
489
|
+
async complete() {
|
|
490
|
+
callCount++;
|
|
491
|
+
return { content: '# No frontmatter', provider: 'counting' };
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest()), WikiPatchError);
|
|
495
|
+
assert.equal(callCount, 1, 'default maxRetries=0 means exactly one attempt');
|
|
496
|
+
});
|
|
497
|
+
test('synthesizeWikiPage rejects negative maxRetries before provider call', async () => {
|
|
498
|
+
let callCount = 0;
|
|
499
|
+
const provider = {
|
|
500
|
+
name: 'counting',
|
|
501
|
+
async complete() {
|
|
502
|
+
callCount++;
|
|
503
|
+
return { content: validPatch(), provider: 'counting' };
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest(), { maxRetries: -1 }), RangeError);
|
|
507
|
+
assert.equal(callCount, 0, 'invalid retry configuration should fail before synthesis');
|
|
508
|
+
});
|
|
509
|
+
test('synthesizeWikiPage rejects non-finite maxRetries before provider call', async () => {
|
|
510
|
+
const provider = fixedProvider(validPatch());
|
|
511
|
+
await assert.rejects(() => synthesizeWikiPage(provider, makeRequest(), { maxRetries: Number.NaN }), RangeError);
|
|
512
|
+
});
|
|
513
|
+
// ── Architecture synthesis validation ─────────────────────────────────────
|
|
514
|
+
function validArchitecturePatch(overrides = {}) {
|
|
515
|
+
const headings = [
|
|
516
|
+
'## Executive Architecture Summary',
|
|
517
|
+
'## System and Repository Context',
|
|
518
|
+
'## Major Modules and Responsibilities',
|
|
519
|
+
'## Runtime, Data, and Control-Flow Relationships',
|
|
520
|
+
'## Build, Test, Deployment, and Operational Surfaces',
|
|
521
|
+
'## Cross-Cutting Concerns',
|
|
522
|
+
'## Caveats and Open Questions',
|
|
523
|
+
].filter((heading) => heading !== overrides.omitHeading);
|
|
524
|
+
const body = [
|
|
525
|
+
'# Architecture',
|
|
526
|
+
'',
|
|
527
|
+
...headings.flatMap((heading) => [heading, '', 'Grounded architecture content.', '']),
|
|
528
|
+
overrides.bodyExtra ?? '',
|
|
529
|
+
...(overrides.omitHumanNotes ? [] : ['<!-- HUMAN_NOTES_START -->', '<!-- HUMAN_NOTES_END -->']),
|
|
530
|
+
''
|
|
531
|
+
].join('\n');
|
|
532
|
+
return [
|
|
533
|
+
'---',
|
|
534
|
+
'source_commit: "abc123"',
|
|
535
|
+
`kind: ${JSON.stringify(overrides.kind ?? 'architecture')}`,
|
|
536
|
+
'compiled_at: "2026-01-01T00:00:00.000Z"',
|
|
537
|
+
'confidence: "medium"',
|
|
538
|
+
'claim_status: "grounded"',
|
|
539
|
+
`source_paths: ${overrides.source_paths ?? '["src/example.ts"]'}`,
|
|
540
|
+
...(overrides.frontmatterExtra ?? []),
|
|
541
|
+
'---',
|
|
542
|
+
body
|
|
543
|
+
].join('\n');
|
|
544
|
+
}
|
|
545
|
+
test('synthesizeWikiPage accepts architecture patch with required metadata, headings, notes, and source paths', async () => {
|
|
546
|
+
const patch = await synthesizeWikiPage(fixedProvider(validArchitecturePatch()), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] }));
|
|
547
|
+
assert.equal(patch.frontmatter.kind, 'architecture');
|
|
548
|
+
});
|
|
549
|
+
test('synthesizeWikiPage rejects architecture patch missing required heading', async () => {
|
|
550
|
+
await assert.rejects(() => synthesizeWikiPage(fixedProvider(validArchitecturePatch({ omitHeading: '## Caveats and Open Questions' })), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] })), (err) => {
|
|
551
|
+
assert.ok(err instanceof WikiPatchError);
|
|
552
|
+
assert.ok(err.issues.some((issue) => issue.code === 'missing-architecture-heading'));
|
|
553
|
+
return true;
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
test('synthesizeWikiPage rejects architecture patch missing human notes block', async () => {
|
|
557
|
+
await assert.rejects(() => synthesizeWikiPage(fixedProvider(validArchitecturePatch({ omitHumanNotes: true })), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] })), (err) => {
|
|
558
|
+
assert.ok(err instanceof WikiPatchError);
|
|
559
|
+
assert.ok(err.issues.some((issue) => issue.code === 'missing-human-notes-block'));
|
|
560
|
+
return true;
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
test('synthesizeWikiPage rejects architecture patch that writes inside the human notes block', async () => {
|
|
564
|
+
await assert.rejects(() => synthesizeWikiPage(fixedProvider(validArchitecturePatch({ bodyExtra: '<!-- HUMAN_NOTES_START -->\nDo not synthesize human notes.\n<!-- HUMAN_NOTES_END -->' })), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] })), (err) => {
|
|
565
|
+
assert.ok(err instanceof WikiPatchError);
|
|
566
|
+
assert.ok(err.issues.some((issue) => issue.code === 'non-empty-human-notes-block'));
|
|
567
|
+
return true;
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
test('synthesizeWikiPage rejects architecture patch with out-of-context source_paths', async () => {
|
|
571
|
+
await assert.rejects(() => synthesizeWikiPage(fixedProvider(validArchitecturePatch({ source_paths: '["src/other.ts"]' })), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] })), (err) => {
|
|
572
|
+
assert.ok(err instanceof WikiPatchError);
|
|
573
|
+
assert.ok(err.issues.some((issue) => issue.code === 'out-of-context-source-paths'));
|
|
574
|
+
return true;
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
test('synthesizeWikiPage rejects architecture patch missing confidence metadata', async () => {
|
|
578
|
+
const content = validArchitecturePatch().replace('confidence: "medium"\n', '');
|
|
579
|
+
await assert.rejects(() => synthesizeWikiPage(fixedProvider(content), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] })), (err) => {
|
|
580
|
+
assert.ok(err instanceof WikiPatchError);
|
|
581
|
+
assert.ok(err.issues.some((issue) => issue.code === 'missing-confidence'));
|
|
582
|
+
return true;
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
test('synthesizeWikiPage rejects architecture patch missing source_paths', async () => {
|
|
586
|
+
const content = validArchitecturePatch().replace('source_paths: ["src/example.ts"]\n', '');
|
|
587
|
+
await assert.rejects(() => synthesizeWikiPage(fixedProvider(content), makeRequest({ archetype: 'architecture', pageName: 'Architecture', pageTitle: 'Architecture', sourcePaths: ['src/example.ts'] })), (err) => {
|
|
588
|
+
assert.ok(err instanceof WikiPatchError);
|
|
589
|
+
assert.ok(err.issues.some((issue) => issue.code === 'missing-source-paths' && issue.level === 'error'));
|
|
590
|
+
return true;
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
// ── WikiPatchError ─────────────────────────────────────────────────────────
|
|
594
|
+
test('WikiPatchError has correct name', () => {
|
|
595
|
+
const err = new WikiPatchError('msg', 'Page', []);
|
|
596
|
+
assert.equal(err.name, 'WikiPatchError');
|
|
597
|
+
});
|
|
598
|
+
test('WikiPatchError is instanceof Error', () => {
|
|
599
|
+
const err = new WikiPatchError('msg', 'Page', []);
|
|
600
|
+
assert.ok(err instanceof Error);
|
|
601
|
+
});
|
|
602
|
+
test('WikiPatchError exposes pageName and issues', () => {
|
|
603
|
+
const issues = [
|
|
604
|
+
{ level: 'error', code: 'empty-content', message: 'empty' },
|
|
605
|
+
];
|
|
606
|
+
const err = new WikiPatchError('Failed', 'My-Page', issues);
|
|
607
|
+
assert.equal(err.pageName, 'My-Page');
|
|
608
|
+
assert.deepEqual(err.issues, issues);
|
|
609
|
+
});
|
|
610
|
+
//# sourceMappingURL=wiki-patch.test.js.map
|