From b911666f3cd02a3953a6a4fd33cba7fa219cafec Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 18 May 2026 18:10:06 -0400 Subject: [PATCH] docs: fix 34 broken in-doc cross-references; extend link-check CI (closes #62) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sibling of #58: the same canonical-name mismatch and relative-path errors that broke README links also broke 34 in-doc cross-references across 14 files. A reader on docs/core-concepts/runtime-engine.md who clicked "Memory" or "Tools" got a GitHub 404; docs/security/overview.md alone had 10 broken links to half a dozen renamed siblings. Three causes per the issue: 1. Missing ../ (21 links): authors wrote paths as if relative to docs/ root, but Markdown resolves relative to the file's own directory. From docs/core-concepts/* and docs/skills/*, links like security/guardrails.md resolved to docs/core-concepts/security/guardrails.md (404) instead of docs/security/guardrails.md. 2. Canonical-name mismatch (10 links, all in security/overview.md): egress.md/secrets.md/signing.md became egress-control.md/ secret-management.md/build-signing.md when the docs were reorganized, plus four ../*.md links pointing at flat names that never existed. 3. Wrong base (3 links): contributing.md linked to ../README.md (= docs/README.md, missing) instead of ../../README.md; command-integration.md cross-references in deployment/ and getting-started/ needed ../reference/ prefix. The fix is purely mechanical link substitution with anchor preservation — no doc content reorganized, no new files created. Workflow change (.github/workflows/docs-links.yaml, added in #61): Renamed job readme-links -> doc-links and extended the Python script to walk docs/**/*.md in addition to README.md. Same fail-on-broken behavior, same path filter. Local dry-run reports 'Checked 37 doc files: all relative .md links resolve'. Audit results on main after this commit: before: 34 broken across 14 doc files after: 0 broken; 37 files checked --- .github/workflows/docs-links.yaml | 33 ++++++++++++++---------- docs/core-concepts/channels.md | 2 +- docs/core-concepts/hooks.md | 4 +-- docs/core-concepts/how-forge-works.md | 4 +-- docs/core-concepts/runtime-engine.md | 6 ++--- docs/core-concepts/skill-md-format.md | 2 +- docs/core-concepts/tools-and-builtins.md | 10 +++---- docs/deployment/production-checklist.md | 2 +- docs/getting-started/contributing.md | 2 +- docs/reference/cli-reference.md | 2 +- docs/reference/web-dashboard.md | 2 +- docs/security/overview.md | 20 +++++++------- docs/skills/embedded-skills.md | 4 +-- docs/skills/skills-cli.md | 2 +- docs/skills/writing-custom-skills.md | 4 +-- 15 files changed, 53 insertions(+), 46 deletions(-) diff --git a/.github/workflows/docs-links.yaml b/.github/workflows/docs-links.yaml index e8f8673..5c318c7 100644 --- a/.github/workflows/docs-links.yaml +++ b/.github/workflows/docs-links.yaml @@ -18,29 +18,36 @@ permissions: contents: read jobs: - readme-links: - name: README link check + doc-links: + name: Doc link check (README + docs/**) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Verify relative .md links in README resolve + - name: Verify relative .md links resolve run: | python3 - <<'PY' import re, os, sys - text = open("README.md").read() + targets = ["README.md"] + for root, _, files in os.walk("docs"): + for f in files: + if f.endswith(".md"): + targets.append(os.path.join(root, f)) bad = [] - for m in re.finditer(r"\[([^\]]+)\]\(([^)]+\.md)(#[^)]*)?\)", text): - label, target = m.group(1), m.group(2) - if target.startswith("http"): - continue - resolved = os.path.normpath(os.path.join(".", target)) - if not os.path.exists(resolved): - bad.append(f" [{label}]({target})") + for path in targets: + with open(path) as fp: + text = fp.read() + for m in re.finditer(r"\[([^\]]+)\]\(([^)]+\.md)(#[^)]*)?\)", text): + label, target = m.group(1), m.group(2) + if target.startswith("http"): + continue + resolved = os.path.normpath(os.path.join(os.path.dirname(path), target)) + if not os.path.exists(resolved): + bad.append(f" {path}: [{label}]({target})") if bad: - print("Broken relative .md links in README.md:") + print("Broken relative .md links:") for line in bad: print(line) sys.exit(1) - print("README.md: all relative .md links resolve") + print(f"Checked {len(targets)} doc files: all relative .md links resolve") PY diff --git a/docs/core-concepts/channels.md b/docs/core-concepts/channels.md index b20dbda..c658bdd 100644 --- a/docs/core-concepts/channels.md +++ b/docs/core-concepts/channels.md @@ -213,7 +213,7 @@ When channels are configured in `forge.yaml`, the build pipeline automatically: 1. **Includes channel config files** — `slack-config.yaml`, `telegram-config.yaml`, etc. are copied into the Docker build context alongside `forge.yaml` 2. **Adds `--with` to the entrypoint** — The container entrypoint becomes `["forge", "run", "--host", "0.0.0.0", "--with", "slack,telegram"]` 3. **Surfaces channel env vars in the manifests** — Every `_env`-suffixed setting in each `-config.yaml` (e.g. `bot_token_env: SLACK_BOT_TOKEN`) is unioned into the Kubernetes `secrets.yaml` and `deployment.yaml` (via `secretKeyRef`) and into the docker-compose adapter services. Both outputs derive from the same source — see [Kubernetes — Env Var Injection](../deployment/kubernetes.md#env-var-injection) -4. **Handles auth loopback** — When [external auth](runtime.md#external-authentication) is configured, channel adapters authenticate to the A2A server using an internal token, bypassing the external auth provider +4. **Handles auth loopback** — When [external auth](runtime-engine.md#external-authentication) is configured, channel adapters authenticate to the A2A server using an internal token, bypassing the external auth provider Pass channel secrets via environment variables: diff --git a/docs/core-concepts/hooks.md b/docs/core-concepts/hooks.md index 44d9318..56438a8 100644 --- a/docs/core-concepts/hooks.md +++ b/docs/core-concepts/hooks.md @@ -79,7 +79,7 @@ hooks.Register(engine.BeforeToolExec, func(ctx context.Context, hctx *engine.Hoo `AfterToolExec` hooks can modify `hctx.ToolOutput` to redact sensitive content before it enters the LLM context. The agent loop reads back `ToolOutput` from the `HookContext` after all hooks fire. -The runner registers a guardrail hook that scans tool output for secrets and PII patterns. The hook passes `hctx.ToolName` to the guardrail engine, enabling per-tool exemptions via `allow_tools` config. See [Tool Output Scanning](security/guardrails.md#tool-output-scanning) for details. +The runner registers a guardrail hook that scans tool output for secrets and PII patterns. The hook passes `hctx.ToolName` to the guardrail engine, enabling per-tool exemptions via `allow_tools` config. See [Tool Output Scanning](../security/guardrails.md#tool-output-scanning) for details. ```go hooks.Register(engine.AfterToolExec, func(ctx context.Context, hctx *engine.HookContext) error { @@ -101,7 +101,7 @@ When skills declare guardrails in their `SKILL.md` frontmatter, the runner regis These hooks complement the global guardrail hooks (secrets/PII scanning) and fire in addition to them. Skill guardrails are loaded from build artifacts or parsed at runtime from `SKILL.md` — no `forge build` step is required. -For pattern syntax and configuration, see [Skill Guardrails](security/guardrails.md#skill-guardrails). +For pattern syntax and configuration, see [Skill Guardrails](../security/guardrails.md#skill-guardrails). ## Audit Logging diff --git a/docs/core-concepts/how-forge-works.md b/docs/core-concepts/how-forge-works.md index 792090c..62bc067 100644 --- a/docs/core-concepts/how-forge-works.md +++ b/docs/core-concepts/how-forge-works.md @@ -53,7 +53,7 @@ Messaging platform integrations that implement the `channels.ChannelPlugin` inte ### forge-ui — Web Dashboard -Local web dashboard for managing agents from the browser. Single Go module embedded into the `forge` binary. See [Dashboard](dashboard.md) for details. +Local web dashboard for managing agents from the browser. Single Go module embedded into the `forge` binary. See [Dashboard](../reference/web-dashboard.md) for details. ### forge-skills — Skill System @@ -360,4 +360,4 @@ The A2A server adds: - **Rate limiting** — Per-IP token bucket middleware (read: 60 req/min burst 10, write: 10 req/min burst 3) with 429 responses and `Retry-After` headers; stale visitors evicted automatically - **Request size limits** — `MaxHeaderBytes` (1 MiB) and `http.MaxBytesReader` (2 MiB) on request bodies; returns 413 on excess -See [Egress Security](security/egress.md) for details. +See [Egress Security](../security/egress-control.md) for details. diff --git a/docs/core-concepts/runtime-engine.md b/docs/core-concepts/runtime-engine.md index 63d82f3..f0fd311 100644 --- a/docs/core-concepts/runtime-engine.md +++ b/docs/core-concepts/runtime-engine.md @@ -243,17 +243,17 @@ The runtime configures a `FilesDir` for tool-generated files (e.g., from `file_c memory/ ← long-term memory ``` -The `FilesDir` is set via `LLMExecutorConfig.FilesDir` and made available to tools through `runtime.FilesDirFromContext(ctx)`. See [Tools — File Create](tools.md#file-create) for details. +The `FilesDir` is set via `LLMExecutorConfig.FilesDir` and made available to tools through `runtime.FilesDirFromContext(ctx)`. See [Tools — File Create](tools-and-builtins.md#file-create) for details. ## Conversation Memory -For details on session persistence, context window management, compaction, and long-term memory, see [Memory](memory.md). +For details on session persistence, context window management, compaction, and long-term memory, see [Memory](memory-system.md). ## Hooks The engine fires hooks at key points in the loop. See [Hooks](hooks.md) for details. -The runner registers five hook groups: logging, audit, progress, global guardrail hooks, and skill guardrail hooks. Global guardrails use the `GuardrailChecker` interface backed by the `github.com/initializ/guardrails` library — the `AfterToolExec` hook scans tool output for secrets and PII, redacting or blocking before results enter the LLM context. Guardrail config is loaded from `guardrails.json` (file mode) or MongoDB (DB mode). Skill guardrail hooks enforce domain-specific rules declared in `SKILL.md` — blocking commands, redacting output, intercepting capability enumeration probes, and replacing binary-enumerating responses. Skill guardrails are loaded from build artifacts or parsed directly from `SKILL.md` at runtime (no `forge build` required). See [Guardrails](security/guardrails.md) for full details. +The runner registers five hook groups: logging, audit, progress, global guardrail hooks, and skill guardrail hooks. Global guardrails use the `GuardrailChecker` interface backed by the `github.com/initializ/guardrails` library — the `AfterToolExec` hook scans tool output for secrets and PII, redacting or blocking before results enter the LLM context. Guardrail config is loaded from `guardrails.json` (file mode) or MongoDB (DB mode). Skill guardrail hooks enforce domain-specific rules declared in `SKILL.md` — blocking commands, redacting output, intercepting capability enumeration probes, and replacing binary-enumerating responses. Skill guardrails are loaded from build artifacts or parsed directly from `SKILL.md` at runtime (no `forge build` required). See [Guardrails](../security/guardrails.md) for full details. ## Streaming diff --git a/docs/core-concepts/skill-md-format.md b/docs/core-concepts/skill-md-format.md index 89d1086..02c5f36 100644 --- a/docs/core-concepts/skill-md-format.md +++ b/docs/core-concepts/skill-md-format.md @@ -128,7 +128,7 @@ Skill scripts run in a restricted environment via `SkillCommandExecutor`: - **OAuth token resolution**: When `OPENAI_API_KEY` is set to `__oauth__`, the executor resolves OAuth credentials and injects the access token, `OPENAI_BASE_URL`, and the configured model as `REVIEW_MODEL` - **Configurable timeout**: Each skill declares a `timeout_hint` in its YAML frontmatter (e.g., 300s for research) - **No shell execution**: Scripts run via `bash