Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
00d0509
fix(icons): fall back to default react-icon when config.json omits an…
Vidigal-code Jun 15, 2026
2a2952e
fix(release): pin internal deps to semver ranges so published package…
Vidigal-code Jun 15, 2026
b434f72
fix(cli): resolve tsx from the package location, not the user's cwd
Vidigal-code Jun 15, 2026
e2ee3f6
chore(cli): bump to 1.1.48 to ship the tsx-resolution fix
Vidigal-code Jun 15, 2026
4594720
fix(icons): align fallback react-icon tags with config.json; default …
Vidigal-code Jun 15, 2026
d7cd9e3
chore(release): bump tools, mcp and cli to 1.1.48
Vidigal-code Jun 15, 2026
f75b5ff
chore: sync pnpm-lock for 1.1.48 internal dep ranges
Vidigal-code Jun 15, 2026
97a6f42
feat(cli): prompt for owner/repo on deploy instead of crashing
Vidigal-code Jun 15, 2026
ac3d35b
chore(release): bump tools, mcp and cli to 1.1.50
Vidigal-code Jun 15, 2026
cd40c6a
feat(cli): make the rest of the deploy flow interactive instead of cr…
Vidigal-code Jun 15, 2026
6e7a5b8
feat(tools): shared doc-access key scheme (double-hash, runtime-agnos…
Vidigal-code Jun 15, 2026
e75c22e
feat(cli): `gitpagedocs password` command + docs-access config genera…
Vidigal-code Jun 15, 2026
29d4cf1
feat(frontend): documentation-wide password gate + chatbot i18n from …
Vidigal-code Jun 15, 2026
ee7bb19
chore(release): regenerate config + baseline, bump to 1.1.51
Vidigal-code Jun 15, 2026
d4324ff
feat(cli): `gitpagedocs ai` generates docs in the gitpagedocs pattern
Vidigal-code Jun 15, 2026
1642a67
docs: document the password gate + AI-pattern generation; refresh man…
Vidigal-code Jun 15, 2026
8092f84
chore(release): bump to 1.1.52 + refresh baseline for new source-viewer
Vidigal-code Jun 15, 2026
d7eff7b
fix(icons): config-matching fallbacks for header controls + refresh docs
Vidigal-code Jun 15, 2026
d32e798
fix(config): backfill site defaults so old config.json inherits curre…
Vidigal-code Jun 15, 2026
608a413
feat(layouts+config): add VSCode light/dark themes, backfill translat…
Vidigal-code Jun 15, 2026
f2059b1
update templates
Vidigal-code Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Keep pnpm linking the in-repo workspace packages (@gitpagedocs/tools, /mcp)
# even though their dependents declare plain semver ranges (^1.1.x) instead of
# the `workspace:*` protocol. Plain ranges are used so the PUBLISHED packages
# carry resolvable npm deps regardless of whether they are released with
# `pnpm publish` or `npm publish` (npm does not rewrite `workspace:*`).
link-workspace-packages=true
prefer-workspace-packages=true
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ This is a pnpm + turbo monorepo: `frontend/` (Next.js viewer), `cli/` (the publi
- `gitpagedocs config` — show the resolved gitpagedocs config
- `gitpagedocs provider [id]` — list AI providers or show one
- `gitpagedocs models [provider]` — list catalog models
- `gitpagedocs document[:repo|:file|:folder]` — generate documentation with AI
- `gitpagedocs ai` — interactive AI docs generator (writes pages in the gitpagedocs pattern)
- `gitpagedocs document[:repo|:file|:folder]` — generate documentation with AI in the gitpagedocs pattern
- `gitpagedocs password` — set a documentation access password (writes the public key to config.json)
- `gitpagedocs deploy | pages` — configure GitHub Pages via Actions and push
- `gitpagedocs doctor` — diagnose the environment
- `gitpagedocs mcp start` — start the MCP server over stdio
Expand Down
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,17 @@ This mode provides:
- optional `.gitpagedocsconfig` persistence for manual reuse
- interactive fallback when directories are missing (fix/skip/abort)

**Generates in the gitpagedocs pattern.** The model is told what gitpagedocs is and
returns documentation split into multiple pages. `gitpagedocs ai` first scaffolds the base
`gitpagedocs/` structure, then writes each page to
`gitpagedocs/docs/versions/<latest>/<lang>/<slug>.md` (in every language) **and wires them
into that version's `config.json`** (`routes-md` + `menus-header-md`), so the AI pages show up
directly in the docs viewer menu. The added entries are tagged `aiGenerated` and are
idempotent — re-running `gitpagedocs ai` replaces them instead of duplicating.

> Note: a later plain `gitpagedocs` run rebuilds the base config from the deterministic
> templates and drops the AI wiring — re-run `gitpagedocs ai` to restore it.

### Manual config (`.gitpagedocsconfig`)

You can run manually with a persisted config in repository root:
Expand All @@ -495,6 +506,23 @@ You can run manually with a persisted config in repository root:

For Ollama, use `baseUrl` instead of `apiKey`.

## Documentation password gate

Protect the whole documentation site behind a password:

```bash
gitpagedocs password
```

It prompts for a password (type + confirm), writes a non-reversible **public key** to
`site.docsAccess` in `gitpagedocs/config.json`, and prints a **private key** to copy. The
scheme is double-hash: `privateKey = SHA256(password)`, `publicKey = SHA256(privateKey)` — the
password itself is never stored. When `docsAccess.enabled` is set, the viewer blocks the entire
documentation behind a full-page gate; visitors unlock with the **password OR the private key**
(verified against the public key). The unlock is cached in `localStorage`, and a lock icon in
the menu clears the cache to re-block. Leave `docsAccess.enabled` false (the default) to keep
the docs open.

## Configuration File Format

Runtime supports three config file formats (in order of precedence):
Expand Down Expand Up @@ -533,7 +561,9 @@ ISC. See [repository](https://github.com/Vidigal-code/git-page-docs) for details
- `gitpagedocs config` — show the resolved gitpagedocs config
- `gitpagedocs provider [id]` — list AI providers or show one
- `gitpagedocs models [provider]` — list catalog models
- `gitpagedocs document[:repo|:file|:folder]` — generate documentation with AI
- `gitpagedocs ai` — interactive AI docs generator (writes pages in the gitpagedocs pattern)
- `gitpagedocs document[:repo|:file|:folder]` — generate documentation with AI in the gitpagedocs pattern
- `gitpagedocs password` — set a documentation access password (writes the public key to config.json)
- `gitpagedocs deploy | pages` — configure GitHub Pages via Actions and push
- `gitpagedocs doctor` — diagnose the environment
- `gitpagedocs mcp start` — start the MCP server over stdio
Expand Down
18 changes: 14 additions & 4 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ npx @gitpagedocs/cli
| `gitpagedocs --push --owner <o> --repo <r> [--path <sub>]` | Generate, configure Pages, create workflow, commit + push |
| `gitpagedocs --home` | Standalone distribution (`gitpagedocshome/`: static site + .env + Dockerfile) |
| `gitpagedocs -i` / `--interactive` | Interactive prompts |
| `gitpagedocs ai` | Interactive AI documentation generator (see below) |
| `gitpagedocs ai` | Interactive AI documentation generator — writes pages in the gitpagedocs pattern (see below) |
| `gitpagedocs provider [id]` · `models [provider]` | List AI providers / catalog models |
| `gitpagedocs document[:repo\|:file\|:folder]` | Generate documentation with AI |
| `gitpagedocs document[:repo\|:file\|:folder]` | Generate documentation with AI in the gitpagedocs pattern |
| `gitpagedocs password` | Set a documentation access password (writes the public key to `config.json`, prints the private key) |
| `gitpagedocs deploy` / `pages [actions\|deploy]` | Configure GitHub Pages via Actions + push |
| `gitpagedocs docs` | Refresh managed regions of README/CONTRIBUTING/SECURITY |
| `gitpagedocs doctor` · `version` · `update` | Diagnostics / version / update hint |
Expand Down Expand Up @@ -134,9 +135,18 @@ npx gitpagedocs ai
- asks provider (`openai`, `claude`, `gemini`, `ollama`)
- asks API key or Ollama URL
- asks paths to scan (supports one or many paths, including other repositories)
- generates markdown documentation in `pt`, `en`, `es`
- generates documentation in `pt`, `en`, `es` **in the gitpagedocs pattern**
- optionally persists config in `.gitpagedocsconfig`
- optionally runs standard `gitpagedocs` scaffolding after AI generation

### gitpagedocs pattern output

The model is given a gitpagedocs-aware system prompt and returns documentation split into
pages (delimited by `=== PAGE: <slug> | <Title> ===`). The CLI scaffolds the base
`gitpagedocs/` structure first, then writes each page to
`gitpagedocs/docs/versions/<latest>/<lang>/<slug>.md` for every language and **wires it into
that version's `config.json`** (`routes-md` + `menus-header-md`) so the pages render in the
viewer menu. Entries are tagged `aiGenerated` and idempotent (re-running replaces them). A
later plain `gitpagedocs` rebuild drops the wiring — re-run `gitpagedocs ai` to restore.

### `.gitpagedocsconfig` (manual mode)

Expand Down
68 changes: 68 additions & 0 deletions cli/ai/application/docs-pattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Parse an AI documentation response into gitpagedocs pages.
*
* The model is instructed (see GITPAGEDOCS_DOC_SYSTEM_PROMPT) to delimit each
* page with a line `=== PAGE: <slug> | <Title> ===`. This splits that response
* into individual pages. If the model ignored the format, the whole response is
* returned as a single "ai-overview" page so content is never lost.
*/

export interface AiDocPage {
/** lowercase-kebab, English, identical across languages (used as the filename) */
slug: string;
title: string;
body: string;
}

const PAGE_DELIMITER = /^===\s*PAGE:\s*(.+?)\s*\|\s*(.*?)\s*===\s*$/;

/** Turn an arbitrary heading into a safe lowercase-kebab slug. */
export function slugify(raw: string): string {
const slug = (raw ?? "")
.toLowerCase()
.normalize("NFKD")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 60);
return slug || "page";
}

export function parseAiPages(markdown: string): AiDocPage[] {
const text = (markdown ?? "").replace(/\r\n/g, "\n").trim();
if (!text) return [];

const pages: AiDocPage[] = [];
let current: { slug: string; title: string; body: string[] } | null = null;

const flush = () => {
if (current) {
pages.push({ slug: current.slug, title: current.title, body: current.body.join("\n").trim() });
}
};

for (const line of text.split("\n")) {
const match = line.match(PAGE_DELIMITER);
if (match) {
flush();
const rawSlug = match[1].trim();
const title = match[2].trim() || rawSlug;
current = { slug: slugify(rawSlug), title, body: [] };
} else if (current) {
current.body.push(line);
}
}
flush();

const withBody = pages.filter((page) => page.body.length > 0);
if (withBody.length === 0) {
return [{ slug: "ai-overview", title: "Overview", body: text }];
}

// De-duplicate slugs within a single response (suffix later collisions).
const seen = new Map<string, number>();
return withBody.map((page) => {
const count = seen.get(page.slug) ?? 0;
seen.set(page.slug, count + 1);
return count === 0 ? page : { ...page, slug: `${page.slug}-${count + 1}` };
});
}
44 changes: 32 additions & 12 deletions cli/ai/application/run-ai-cli-command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import path from "node:path";
import { AiCommandService } from "./ai-command";
import { createAiProvider } from "./ai-provider-factory";
import { FileSystemAdapter, type FilePayload } from "../infrastructure/file-system-adapter";
Expand All @@ -7,6 +6,11 @@ import {
} from "../infrastructure/ai-config-file";
import type { AiCliRunPlan, AiCliRunSummary } from "../core/models/ai-cli-config";
import { promptMissingDirectories, runAiInteractivePrompt } from "../presentation/ai-prompts";
import { GITPAGEDOCS_DOC_SYSTEM_PROMPT } from "../config";
import { parseAiPages, type AiDocPage } from "./docs-pattern";
import { writeVersionDocs } from "../infrastructure/version-docs-writer";
// @ts-expect-error .mjs runtime module is type-less in this package.
import { DOC_VERSIONS } from "../../contracts/doc-versions.mjs";

const LANGUAGE_TO_LABEL: Record<string, string> = {
pt: "Portuguese",
Expand All @@ -22,7 +26,13 @@ function aggregateFilesPayload(files: FilePayload[]): string {

function buildLanguagePrompt(basePrompt: string, language: "pt" | "en" | "es"): string {
const languageLabel = LANGUAGE_TO_LABEL[language] ?? language;
return `${basePrompt}\n\nMandatory output rules:\n- Respond 100% in ${languageLabel}.\n- Generate professional markdown for technical documentation.\n- Include architectural overview, layer responsibilities, and next steps.\n- Avoid content outside the scope of the analyzed files.`;
const authorGuidance = basePrompt?.trim() ? `Additional author guidance:\n${basePrompt.trim()}\n\n` : "";
return (
`${GITPAGEDOCS_DOC_SYSTEM_PROMPT}\n\n` +
authorGuidance +
`Output language: write every page Title and body 100% in ${languageLabel}. ` +
`Keep each page slug lowercase-kebab and in English so the languages line up.`
);
}

async function collectFilesWithInteractiveFallback(
Expand Down Expand Up @@ -66,7 +76,7 @@ async function collectFilesWithInteractiveFallback(
};
}

async function runPlan(plan: AiCliRunPlan): Promise<AiCliRunSummary> {
async function runPlan(plan: AiCliRunPlan, cwd: string): Promise<AiCliRunSummary> {
const provider = createAiProvider({
provider: plan.config.ai.provider,
model: plan.config.ai.model,
Expand All @@ -92,30 +102,33 @@ async function runPlan(plan: AiCliRunPlan): Promise<AiCliRunSummary> {
}

const payload = aggregateFilesPayload(files);
const outputs: string[] = [];
const pagesByLang: Partial<Record<"pt" | "en" | "es", AiDocPage[]>> = {};

for (const language of plan.config.ai.languages) {
const prompt = buildLanguagePrompt(plan.config.ai.contextPrompt, language);
const markdown = await aiService.runGeneration(payload, prompt);
const outputFile = path.posix.join(
plan.config.ai.outputDir.replace(/\\/g, "/"),
`${plan.config.ai.filePrefix}-${plan.config.ai.provider}-${language}.md`,
);
await fileSystem.writeDocumentationOutput(markdown, outputFile);
outputs.push(outputFile);
pagesByLang[language] = parseAiPages(markdown);
}

// Wire the AI pages into the latest documentation version (gitpagedocs pattern).
const versionId = DOC_VERSIONS[DOC_VERSIONS.length - 1] as string;
const result = await writeVersionDocs({ cwd, versionId, pagesByLang });

return {
scannedDirectories: scanned,
skippedDirectories: skipped,
scannedFilesCount: files.length,
outputs,
outputs: result.files,
pages: result.slugs,
};
}

export async function runAiCliCommand(options: {
cwd: string;
onInfo?: (message: string) => void;
/** Provided by the composition root to scaffold the base gitpagedocs/ tree
* (so the version config.json exists before AI pages are wired into it). */
onScaffold?: () => Promise<void>;
}): Promise<{ summary: AiCliRunSummary; runConfigScaffold: boolean }> {
const logInfo = options.onInfo ?? (() => undefined);
const configRepo = new AiConfigFileRepository(options.cwd);
Expand All @@ -128,7 +141,14 @@ export async function runAiCliCommand(options: {
logInfo(`[gitpagedocs:ai] Configuration saved to ${configRepo.getConfigPath()}`);
}

const summary = await runPlan(plan);
// Build the base gitpagedocs structure BEFORE generation so the version
// config.json exists and the AI pages can be wired into it.
if (plan.runConfigScaffold && options.onScaffold) {
logInfo("[gitpagedocs] Generating base gitpagedocs structure...");
await options.onScaffold();
}

const summary = await runPlan(plan, options.cwd);
return {
summary,
runConfigScaffold: plan.runConfigScaffold,
Expand Down
28 changes: 28 additions & 0 deletions cli/ai/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,32 @@ export const AI_MODEL_DEFAULTS: Readonly<Record<AiProviderId, string>> = {
export const DEFAULT_AI_DOC_PROMPT =
'You are a senior tech writer. Describe the provided code.';

/**
* System prompt that teaches the model what gitpagedocs is and forces it to
* return documentation in the gitpagedocs pattern: a sequence of pages, each
* introduced by a machine-parseable delimiter line so the CLI can split the
* response into individual versioned markdown pages and wire them into the
* viewer's config.json (routes-md + menus-header-md).
*/
export const GITPAGEDOCS_DOC_SYSTEM_PROMPT = [
'You are the documentation engine for "gitpagedocs", a generator of multilingual,',
'versioned static documentation sites. gitpagedocs renders markdown pages from',
'gitpagedocs/docs/versions/<version>/<lang>/<page>.md and wires them into a viewer',
'through a config.json contract (routes + header menus). Your job is to analyze the',
'provided source code and produce clean, professional documentation that fits this',
'pattern so it can be dropped straight into a gitpagedocs site.',
'',
'STRICT OUTPUT FORMAT — return ONLY a sequence of pages. Begin every page with a',
'delimiter line on its own, exactly:',
'=== PAGE: <slug> | <Title> ===',
'followed by the page body in GitHub-flavored markdown. Rules:',
'- Produce 4 to 8 cohesive pages. Suggested slugs: overview, getting-started,',
' architecture, configuration, usage, deployment (adapt to the project).',
'- <slug> MUST be lowercase-kebab-case and in ENGLISH, and identical across every',
' language run, so the languages line up (only the Title and body are translated).',
'- Do NOT wrap the whole answer in a code fence. Do NOT add any preamble or epilogue',
' outside the page blocks. The first line of your reply MUST be a "=== PAGE:" line.',
'- Base everything strictly on the analyzed files; do not invent features.',
].join('\n');

export const OLLAMA_DEFAULT_BASE_URL = 'http://localhost:11434';
2 changes: 2 additions & 0 deletions cli/ai/core/models/ai-cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ export interface AiCliRunSummary {
skippedDirectories: string[];
scannedFilesCount: number;
outputs: string[];
/** Slugs of the gitpagedocs pages wired into the version config (if any). */
pages?: string[];
}
Loading
Loading