docs: add usage and developer documentation

Add user and developer documentation plus Pagenary docsite publishing support.

Includes Getting Started, Developer Guide, API and MCP references, Pagenary tenant metadata, npm scripts for sync/build/check/serve, and validation coverage for the generated docsite.

Closes #102
This commit is contained in:
Joseph Magly authored and GitHub committed 2026-07-20 12:59:53 -04:00
1 parent 7b1d12a07c
commit 0c35a833b9
39 files changed
+4920 -2

No files matched your search

+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env node
import { access, readFile } from 'node:fs/promises';
import { join } from 'node:path';
const ROOT = process.cwd();
const TENANT_ID = 't3mp3st-docs';
const DIST = join(ROOT, 'dist', TENANT_ID);
const TENANT = join(ROOT, 'docsite', TENANT_ID);
async function exists(path) {
try {
await access(path);
return true;
} catch {
return false;
}
}
async function readJson(path) {
return JSON.parse(await readFile(path, 'utf8'));
}
function fail(message) {
console.error(`✖ ${message}`);
process.exitCode = 1;
}
async function main() {
const required = [
'index.html',
'manifest.js',
'sitemap.xml',
'robots.txt',
'llms.txt',
'search-index/manifest.json',
'search-index/metadata.json',
'content-index.json',
'documents.jsonl',
];
for (const rel of required) {
if (!(await exists(join(DIST, rel)))) fail(`missing ${join('dist', TENANT_ID, rel)}`);
}
const manifest = await readJson(join(TENANT, 'manifest.json'));
const expectedPages = flattenSections(manifest.sections || []).filter((section) => section.file).length;
const metadata = await readJson(join(DIST, 'search-index', 'metadata.json'));
const indexedPages = Array.isArray(metadata.pages) ? metadata.pages.length : 0;
if (indexedPages !== expectedPages) {
fail(`search metadata indexed ${indexedPages} page(s), expected ${expectedPages}`);
}
const sitemap = await readFile(join(DIST, 'sitemap.xml'), 'utf8');
const sitemapUrls = [...sitemap.matchAll(/<url>/g)].length;
if (sitemapUrls < expectedPages) {
fail(`sitemap has ${sitemapUrls} URL(s), expected at least ${expectedPages}`);
}
const llms = await readFile(join(DIST, 'llms.txt'), 'utf8');
if (!llms.includes('T3MP3ST Documentation') || !llms.includes('Getting Started')) {
fail('llms.txt does not include expected site/page metadata');
}
if (process.exitCode) process.exit(process.exitCode);
console.log(`Pagenary docsite artifacts verified: ${expectedPages} pages indexed.`);
}
function flattenSections(sections) {
const out = [];
for (const section of sections || []) {
out.push(section);
out.push(...flattenSections(section.sections || section.subsections || []));
}
return out;
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
+424
View File
@@ -0,0 +1,424 @@
#!/usr/bin/env node
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
const ROOT = process.cwd();
const TENANT_ID = 't3mp3st-docs';
const TENANT_DIR = join(ROOT, 'docsite', TENANT_ID);
const CONTENT_DIR = join(TENANT_DIR, 'content');
const DEFAULT_SITE_URL = 'https://elder-plinius.github.io/T3MP3ST/t3mp3st-docs';
const pages = [
{
source: 'docs/README.md',
output: 'README.md',
id: 'welcome',
title: 'T3MP3ST Documentation',
summary: 'Documentation map for operators, developers, benchmarks, and release work.',
category: 'overview',
audience: ['operator', 'developer'],
},
{
source: 'docs/GETTING_STARTED.md',
output: 'GETTING_STARTED.md',
id: 'getting-started',
title: 'Getting Started',
summary: 'Install, configure, launch, and run the first safe T3MP3ST workflow.',
category: 'operator',
audience: ['operator'],
},
{
source: 'docs/SCOPE_AND_AUTHORIZATION.md',
output: 'SCOPE_AND_AUTHORIZATION.md',
id: 'scope-and-authorization',
title: 'Scope and Authorization',
summary: 'Rules of engagement, authorization receipts, evidence, and retest boundaries.',
category: 'operator',
audience: ['operator', 'security-reviewer'],
},
{
source: 'docs/TARGET_HEADERS.md',
output: 'TARGET_HEADERS.md',
id: 'target-headers',
title: 'Target Headers',
summary: 'How to inject target-scoped authentication headers safely.',
category: 'operator',
audience: ['operator', 'developer'],
},
{
source: 'docs/ARSENAL_ACTIVATION_PLAN.md',
output: 'ARSENAL_ACTIVATION_PLAN.md',
id: 'arsenal-activation-plan',
title: 'Arsenal Activation Plan',
summary: 'Install optional external tools and understand adapter readiness.',
category: 'operator',
audience: ['operator'],
},
{
source: 'docs/TEAM_PREVIEW.md',
output: 'TEAM_PREVIEW.md',
id: 'team-preview',
title: 'Team Preview',
summary: 'Ten-minute preview path for evaluating the War Room and local-safe flows.',
category: 'operator',
audience: ['operator', 'maintainer'],
},
{
source: 'docs/DEVELOPER_GUIDE.md',
output: 'DEVELOPER_GUIDE.md',
id: 'developer-guide',
title: 'Developer Guide',
summary: 'Architecture, local development, scripts, extension points, and release checks.',
category: 'developer',
audience: ['developer', 'maintainer'],
},
{
source: 'docs/API_REFERENCE.md',
output: 'API_REFERENCE.md',
id: 'api-reference',
title: 'API Reference',
summary: 'Local HTTP API endpoints grouped by workflow.',
category: 'developer',
audience: ['developer', 'integrator'],
},
{
source: 'docs/MCP_GUIDE.md',
output: 'MCP_GUIDE.md',
id: 'mcp-guide',
title: 'MCP Guide',
summary: 'Model Context Protocol setup and security_recon usage.',
category: 'developer',
audience: ['developer', 'integrator'],
},
{
source: 'docs/CONTRIBUTION_RECEIPTS.md',
output: 'CONTRIBUTION_RECEIPTS.md',
id: 'contribution-receipts',
title: 'Contribution Receipts',
summary: 'Evidence template for scoped pull requests and claim changes.',
category: 'developer',
audience: ['contributor', 'maintainer'],
},
{
source: 'docs/PULL_REQUEST_DELIVERY.md',
output: 'PULL_REQUEST_DELIVERY.md',
id: 'pull-request-delivery',
title: 'Pull Request Delivery',
summary: 'Contributor and maintainer checklist for reviewable pull requests.',
category: 'developer',
audience: ['contributor', 'maintainer'],
},
{
source: 'docs/VERIFIED_PROVENANCE.md',
output: 'VERIFIED_PROVENANCE.md',
id: 'verified-provenance',
title: 'Verified Provenance',
summary: 'How findings become tool-proven instead of model-asserted.',
category: 'benchmarks',
audience: ['developer', 'researcher'],
},
{
source: 'docs/XBOW_BASELINE.md',
output: 'XBOW_BASELINE.md',
id: 'xbow-baseline',
title: 'XBOW Baseline',
summary: 'XBEN benchmark baseline and validation notes.',
category: 'benchmarks',
audience: ['researcher', 'maintainer'],
},
{
source: 'docs/CYBENCH.md',
output: 'CYBENCH.md',
id: 'cybench',
title: 'Cybench',
summary: 'Cybench benchmark methodology and results notes.',
category: 'benchmarks',
audience: ['researcher', 'maintainer'],
},
{
source: 'docs/WALL_FORENSICS.md',
output: 'WALL_FORENSICS.md',
id: 'wall-forensics',
title: 'Wall Forensics',
summary: 'Per-challenge misses and benchmark analysis.',
category: 'benchmarks',
audience: ['researcher', 'maintainer'],
},
{
source: 'docs/INTEGRITY_LEDGER.md',
output: 'INTEGRITY_LEDGER.md',
id: 'integrity-ledger',
title: 'Integrity Ledger',
summary: 'Contamination audit and retractions.',
category: 'benchmarks',
audience: ['researcher', 'maintainer'],
},
{
source: 'docs/OBSIDIVM.md',
output: 'OBSIDIVM.md',
id: 'obsidivm',
title: 'Obsidivm',
summary: 'Local web range and evaluation notes.',
category: 'benchmarks',
audience: ['researcher', 'operator'],
},
{
source: 'docs/COGNITIVE_ARCHITECTURE.md',
output: 'COGNITIVE_ARCHITECTURE.md',
id: 'cognitive-architecture',
title: 'Cognitive Architecture',
summary: 'Prompt and reasoning architecture boundaries.',
category: 'benchmarks',
audience: ['researcher', 'developer'],
},
{
source: 'docs/AI_REDTEAM_TECHNIQUES.md',
output: 'AI_REDTEAM_TECHNIQUES.md',
id: 'ai-redteam-techniques',
title: 'AI Red-Team Techniques',
summary: 'Defensive taxonomy and technique map.',
category: 'benchmarks',
audience: ['researcher', 'operator'],
},
{
source: 'docs/INSTALL_MATRIX.md',
output: 'INSTALL_MATRIX.md',
id: 'install-matrix',
title: 'Install Matrix',
summary: 'Operating-system readiness notes.',
category: 'release',
audience: ['operator', 'maintainer'],
},
{
source: 'docs/RELEASE_CHECKLIST.md',
output: 'RELEASE_CHECKLIST.md',
id: 'release-checklist',
title: 'Release Checklist',
summary: 'Release gates and verification steps.',
category: 'release',
audience: ['maintainer'],
},
{
source: 'docs/CHANGELOG.md',
output: 'CHANGELOG.md',
id: 'changelog',
title: 'Changelog',
summary: 'Project change history.',
category: 'release',
audience: ['operator', 'developer', 'maintainer'],
},
];
const groups = [
{
id: 'operators',
title: 'Operators',
summary: 'Install, configure, scope, and review T3MP3ST safely.',
ids: ['getting-started', 'scope-and-authorization', 'target-headers', 'arsenal-activation-plan', 'team-preview'],
},
{
id: 'developers',
title: 'Developers',
summary: 'Integrate, extend, and contribute to T3MP3ST.',
ids: ['developer-guide', 'api-reference', 'mcp-guide', 'contribution-receipts', 'pull-request-delivery'],
},
{
id: 'benchmarks',
title: 'Benchmarks And Provenance',
summary: 'Benchmark methodology, provenance, and research architecture.',
ids: [
'verified-provenance',
'xbow-baseline',
'cybench',
'wall-forensics',
'integrity-ledger',
'obsidivm',
'cognitive-architecture',
'ai-redteam-techniques',
],
},
{
id: 'release',
title: 'Release And Maintenance',
summary: 'Install readiness, release gates, and project history.',
ids: ['install-matrix', 'release-checklist', 'changelog'],
},
];
const pageById = new Map(pages.map((page) => [page.id, page]));
const idByBasename = new Map(pages.map((page) => [page.output.toLowerCase(), page.id]));
function yamlScalar(value) {
return JSON.stringify(String(value));
}
function yamlList(values) {
return `[${values.map((value) => yamlScalar(value)).join(', ')}]`;
}
function stripExistingFrontmatter(markdown) {
return markdown.replace(/^\uFEFF?---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
}
function normalizeMarkdown(markdown) {
return stripExistingFrontmatter(markdown).replace(/\r\n/g, '\n').replace(/\s+$/g, '') + '\n';
}
function rewriteInternalLinks(markdown) {
return markdown.replace(/\]\(([^)]+\.md)(#[^)]+)?\)/gi, (match, rawTarget, rawAnchor = '') => {
const normalizedTarget = String(rawTarget).replace(/\\/g, '/');
const basename = normalizedTarget.split('/').pop().toLowerCase();
const id = idByBasename.get(basename);
if (!id) return match;
const anchor = rawAnchor ? rawAnchor.replace(/^#/, '-') : '';
return `](#${id}${anchor})`;
});
}
function frontmatterFor(page) {
return [
'---',
`title: ${yamlScalar(page.title)}`,
`summary: ${yamlScalar(page.summary)}`,
`description: ${yamlScalar(page.summary)}`,
`audience: ${yamlList(page.audience)}`,
`category: ${yamlScalar(page.category)}`,
'status: "current"',
'source: "T3MP3ST repository"',
`sourcePath: ${yamlScalar(page.source)}`,
'updated: "2026-07-20"',
'---',
'',
].join('\n');
}
function sectionFor(page) {
return {
id: page.id,
title: page.title,
summary: page.summary,
file: page.output,
};
}
function groupContent(group) {
const lines = [
`# ${group.title}`,
'',
group.summary,
'',
'## Pages',
'',
];
for (const id of group.ids) {
const page = pageById.get(id);
lines.push(`- [${page.title}](#${page.id}) - ${page.summary}`);
}
return `${lines.join('\n')}\n`;
}
async function writeJson(filePath, value) {
await mkdir(dirname(filePath), { recursive: true });
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
async function main() {
await rm(TENANT_DIR, { recursive: true, force: true });
await mkdir(CONTENT_DIR, { recursive: true });
for (const page of pages) {
const sourcePath = join(ROOT, page.source);
const outputPath = join(CONTENT_DIR, page.output);
const markdown = rewriteInternalLinks(normalizeMarkdown(await readFile(sourcePath, 'utf8')));
await writeFile(outputPath, `${frontmatterFor(page)}${markdown}`, 'utf8');
}
for (const group of groups) {
const outputPath = join(CONTENT_DIR, `${group.id}.md`);
const page = {
source: `generated:${group.id}`,
title: group.title,
summary: group.summary,
audience: ['operator', 'developer', 'maintainer'],
category: 'navigation',
};
await writeFile(outputPath, `${frontmatterFor(page)}${groupContent(group)}`, 'utf8');
}
const config = {
title: 'T3MP3ST Documentation',
description: 'Operator and developer documentation for T3MP3ST, the local offensive-security command center for authorized testing.',
brandMark: 'T3MP3ST',
brandSub: 'Docs',
tagline: 'Authorized testing, evidence-first workflows, and developer integration.',
copyright: 'T3MP3ST contributors',
language: 'en-US',
layout: 'docs',
navPosition: 'left',
navCollapse: 'overlay',
pageToc: { placement: 'rail', minHeadings: 2 },
codeCopy: true,
readingProgress: true,
accentColor: '#00d4ff',
surfaceColor: '#f7fafc',
seo: {
siteUrl: process.env.PAGENARY_SITE_URL || DEFAULT_SITE_URL,
discoverabilityProfile: 'open',
generateStaticPages: true,
generateSitemap: true,
generateRobotsTxt: true,
generateLlmsTxt: true,
generateCorpusArtifacts: true,
defaultChangeFreq: 'weekly',
},
share: {
enabled: true,
services: ['copy', 'email', 'x', 'linkedin', 'reddit'],
},
};
const manifest = {
default: 'welcome',
sections: [
sectionFor(pageById.get('welcome')),
...groups.map((group) => ({
id: group.id,
title: group.title,
summary: group.summary,
file: `${group.id}.md`,
sections: group.ids.map((id) => sectionFor(pageById.get(id))),
})),
],
};
const tenants = {
tenants: [
{
id: TENANT_ID,
source: { type: 'local', path: `./docsite/${TENANT_ID}` },
strictLinks: true,
language: 'en-US',
accessibility: {
strict: false,
report: { enabled: true },
},
},
],
};
await writeJson(join(TENANT_DIR, 'config.json'), config);
await writeJson(join(TENANT_DIR, 'manifest.json'), manifest);
await writeJson(join(ROOT, 'tenants.json'), tenants);
console.log(`Pagenary tenant synced: ${TENANT_ID}`);
console.log(`Content: ${pages.length} source Markdown pages plus ${groups.length} generated group pages`);
console.log('Build: npm run docs:build');
console.log('Preview: npm run docs:serve');
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});