agentdocs

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package agentdocs renders a contract.Contract into provider-specific agent instruction documents, per ADR-0032 ("Agent-First Development Experience", docs/adr/adr-0032-agent-first-development-experience.md), P1: "Generate portable agent instruction files and repository templates" (Jira MOD-67). The ADR's "Portability" section is the source of truth for what this package produces:

The repository contract is authoritative. Thin adapters may generate:

  - AGENTS.md for OpenAI/Codex and generic repository-aware agents;
  - CLAUDE.md or Claude-specific hooks when available;
  - Kimi project guidance and optional CodeGraph hook setup;
  - MCP discovery metadata and read-only tool descriptions.

No provider-specific hook or global configuration may be required for
the baseline workflow. Agents without hooks must still be able to use
the CLI and contract directly.

This package is the "thin adapter": it turns one contract.Contract into consistent, provider-specific guidance so that AGENTS.md, CLAUDE.md, Kimi guidance, and Codex guidance never drift from the contract or from each other on the facts that matter (commands, verification, protected paths, credentials, handoff format) — only the framing text differs.

Standalone leaf package

agentdocs is a standalone leaf package, like provenance, discovery, verify, and contract: it does not import the core modulex package. It depends only on contract (for the Contract schema) and the standard library. It does not import discovery or verify — the command matrix and verification guidance this package renders are derived entirely from contract.Contract.Commands and contract.Contract.Verification, which already carry everything needed (name, command, class, reason); nothing here needs discovery.ToolStatus or verify.CheckSpec.

Does not touch the filesystem

Generate and Drift are pure functions: string (or contract.Contract) in, string (or bool, error) out. Neither reads nor writes a file. A caller (a future `modulex agent generate` CLI, out of scope for this ticket) is responsible for reading modulex.agent.yaml, unmarshaling it into a contract.Contract, and writing Generate's output to AGENTS.md, CLAUDE.md, or wherever the target's convention places it. This mirrors contract's own "never reads the filesystem beyond what a caller hands it" discipline.

One Generate function, not four

Generate(c, target) takes a Target parameter rather than exposing four separate GenerateAGENTS/GenerateCLAUDE/GenerateKimi/GenerateCodex functions. The four outputs share the same section structure (command matrix, verification, safety policy, handoff) built from the same contract.Contract fields — only the title and introductory framing differ per target (see targetFraming). A single function with a Target switch keeps that shared structure in one place; four separate functions would either duplicate the section-building code four times or still funnel through a shared unexported helper, at which point the exported four-function surface would just be a thinner wrapper around what Generate already is. An unknown Target is a caller bug (a typo, a forgotten case after adding a fifth target), so Generate returns an error rather than panicking or silently emitting an empty/default document.

Hybrid text/template + string-building, not one or the other

The four targets' documents share one overall skeleton (generated-file header, title, intro, source note, then a fixed sequence of sections, then a footer) but differ in the title/intro text. That skeleton is a text/template (docTemplateText) so the four targets' shape stays visibly identical and easy to diff — the same reason contract.RenderText uses plain string-building for one shape, but here there genuinely are four shapes to keep in sync.

Each *section* (command matrix, verification tables, safety policy, projects, boundaries, handoff), however, is built with a small strings.Builder helper (renderCommandsSection, renderSafetySection, ...), not template range/if actions. A markdown table cell needs literal backtick characters for inline code; a text/template *source file* is conventionally written as a Go raw string literal, which cannot itself contain a literal backtick (it would terminate the raw string). Rather than thread a {{.BT}} backtick-escape variable through every table row (workable, but noisy and easy to get wrong across four call sites), each section is rendered to a plain Go string once — where backticks are just ordinary characters in an interpreted string literal — and handed to the skeleton template as an already-formatted, opaque field (renderData.CommandsSection, etc.). template.Execute does not escape template data (that is html/template's job, not text/template's), so inserting a pre-rendered markdown block via {{.CommandsSection}} reproduces it byte-for-byte. This also means the section-building code is written once and shared by all four targets, rather than repeated per target.

All four targets render as Markdown

AGENTS.md and CLAUDE.md are unambiguously Markdown by filename convention. For TargetKimi and TargetCodex, this package also emits Markdown: both are prose guidance documents (Kimi Code CLI project guidance; OpenAI Codex / generic repository-aware agent guidance) meant to be read by an agent or a human, not machine-parsed configuration, so Markdown with an HTML-comment header ("<!-- GENERATED FROM ... -->", valid and inert in Markdown) is the natural format for all four rather than inventing a second comment syntax for no benefit. If a future target needs a genuinely different file format (e.g. a TOML hook config), that target's section of the generated-comment/footer helpers is exactly where format-specific comment syntax would be added.

Deterministic regeneration

Generate(c, target) must produce byte-identical output across repeated calls with the same input. contract.Contract's slice fields (Commands, Verification.Focused/Full, ProtectedPaths, GeneratedPaths, RequiredTools, OptionalServices, RequiredCredentials, Projects, Boundaries) carry no ordering guarantee coming out of YAML — map/slice order from yaml.Unmarshal reflects file order, which a human editing modulex.agent.yaml could reorder without changing meaning. Every such slice is copied and sorted (by Class-then-Name for commands, by Name for checks/services/projects/boundaries, alphabetically for plain string lists) before rendering, mirroring the sorted-slice determinism discipline provenance and discovery already use. Sorting a *copy*, not c's slices in place, matters here specifically because Generate takes contract.Contract by value: a Go value copy of a struct still shares the backing array of any slice field, so sorting in place would silently mutate the caller's Contract out from under it.

Drift detection

Drift(c, target, existingContent) reports whether existingContent (e.g. read by the caller from a checked-in AGENTS.md) differs from what Generate(c, target) would currently produce — "the checked-in file is stale relative to modulex.agent.yaml." Drift never reads a file itself; it only calls Generate and compares strings, so a future CI step or CLI command can wire in the actual file I/O.

See docs/planning/agent-instruction-generation-guide.md for the full guide, including a worked example and how the four targets differ.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Drift

func Drift(c contract.Contract, target Target, existingContent string) (bool, error)

Drift reports whether existingContent differs from what Generate(c, target) would currently produce — i.e. whether a checked-in file (e.g. AGENTS.md) is stale relative to the contract. Drift does not read existingContent from disk itself; the caller is responsible for that, matching this package's no-filesystem-access discipline.

func Generate

func Generate(c contract.Contract, target Target) (string, error)

Generate renders c as target's provider-specific agent instruction document. It returns an error if target is not one of the package's four Target constants. Generate never reads or writes a file (see the package doc comment) and produces byte-identical output for the same (c, target) pair on every call.

Types

type Target

type Target string

Target names one provider-specific rendering of a contract.Contract.

const (
	// TargetAGENTS renders AGENTS.md-flavored guidance: baseline
	// instructions for OpenAI/Codex and any other generic,
	// repository-aware coding agent, per ADR-0032's portability section.
	TargetAGENTS Target = "AGENTS.md"
	// TargetCLAUDE renders CLAUDE.md-flavored guidance: Claude-specific
	// framing for Claude Code sessions.
	TargetCLAUDE Target = "CLAUDE.md"
	// TargetKimi renders Kimi Code CLI project guidance.
	TargetKimi Target = "kimi"
	// TargetCodex renders guidance for the OpenAI Codex CLI/cloud agent
	// and other generic repository-aware agents that read a dedicated
	// instructions document distinct from AGENTS.md.
	TargetCodex Target = "codex"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL