# Development This is the contributor entry point for Promptkit, a reusable Go library. All contributors must read the [architecture policy](policy/architecture.md) before making changes. ## Initial Orientation Before starting work: 1. inspect the working tree and preserve unrelated changes; 2. read the policy, contract, and internal documents listed for the task; 3. inspect the relevant implementation and tests before deciding how to change them; and 4. keep documentation limited to implemented behavior unless an accepted decision or temporary roadmap explicitly owns future work. Start with: - the [architecture policy](policy/architecture.md) for library boundaries, dependency direction, invariants, and non-goals; - the [internal component overview](internal/overview.md) for the current package and component inventory; - the [documentation policy](policy/documentation.md) before changing documentation; - the [testing policy](policy/testing.md) before adding, rewriting, or deleting tests; and - the [release procedure](release.md) for version and publication work. ## Task-Specific Reading Guide | Task | Read before changing | | --- | --- | | Root public API | The [architecture policy](policy/architecture.md), [consumer guide](consumers/pkg-promptkit.md), [testing policy](policy/testing.md), and existing GoDoc. | | Prompt, profile, or schema formats | The [framework format reference](formats.md), owning parser or validator package, and [documentation policy](policy/documentation.md). | | Source loading or validation | The [framework format reference](formats.md), [internal source document](internal/sources.md), and owning package tests. | | Model-client behavior | The [OpenAI-compatible integration contract](integrations/openai-compatible-chat.md), [internal model-client document](internal/llm.md), and owning package tests. | | Internal package implementation | The [architecture policy](policy/architecture.md), [internal component overview](internal/overview.md), and focused internal document listed for that package. | | Tests or test fixtures | The [testing policy](policy/testing.md), owning package, and focused internal document listed by the component overview. | | Maintained example | The [example](../examples/go-library/prepare/main.go), [consumer guide](consumers/pkg-promptkit.md), [framework format reference](formats.md), and [documentation policy](policy/documentation.md). | | Documentation | The [documentation policy](policy/documentation.md) and canonical owner of every affected contract. | | Release preparation or publication | The [release procedure](release.md). | For cross-cutting changes, follow every applicable row. Do not create placeholder documents for packages, APIs, or integrations that do not yet exist. ## Maintainer Validation This section is the canonical local validation workflow for Promptkit. Run every command from the repository root before accepting a change. The test suite and maintained examples are deterministic, offline, and require no real provider credentials. ### Tests, Analysis, Build, And Examples Run the ordinary and race-enabled suites, static analysis, the build, and both maintained consumer examples: ```sh go test ./... go test -race ./... go vet ./... go build ./... go run ./examples/go-library/prepare go run ./examples/go-library/run ``` Both examples must exit successfully. Review their JSON output: preparation must report the selected offline prompt, profile, model, and message count; execution must report the deterministic generated output, successful validation, selected offline model, and usage. Neither command may contact a provider or require credentials. ### Go Formatting Check every tracked Go file. The final command must succeed and the captured list must be empty: ```sh unformatted=$( git ls-files '*.go' | while IFS= read -r go_file do gofmt -l "$go_file" done ) test -z "$unformatted" ``` ### Local Markdown Links Use the Python standard library to verify every repository-relative Markdown target and local heading fragment. The check is offline and prints nothing on success: ```sh python3 - <<'PY' from pathlib import Path import re import subprocess import sys from urllib.parse import unquote root = Path.cwd().resolve() markdown_files = [ root / name for name in subprocess.check_output( ["git", "ls-files", "*.md"], text=True ).splitlines() ] link_pattern = re.compile(r"!?\[[^]]*\]\(([^)]+)\)") heading_pattern = re.compile(r"^#{1,6}\s+(.+?)\s*#*\s*$") scheme_pattern = re.compile(r"^[a-z][a-z0-9+.-]*:", re.IGNORECASE) def markdown_lines(path): in_fence = False fence = "" for line in path.read_text(encoding="utf-8").splitlines(): stripped = line.lstrip() marker = stripped[:3] if marker in {"```", "~~~"}: if not in_fence: in_fence = True fence = marker elif marker == fence: in_fence = False fence = "" continue if not in_fence: yield line anchor_cache = {} def anchors(path): if path in anchor_cache: return anchor_cache[path] found = set() counts = {} for line in markdown_lines(path): match = heading_pattern.match(line) if not match: continue heading = re.sub(r"<[^>]+>", "", match.group(1)).replace("`", "") base = re.sub(r"[^\w\- ]", "", heading.lower()).replace(" ", "-") count = counts.get(base, 0) counts[base] = count + 1 found.add(base if count == 0 else f"{base}-{count}") anchor_cache[path] = found return found failures = [] for source in markdown_files: text = "\n".join(markdown_lines(source)) for match in link_pattern.finditer(text): target = match.group(1).strip() if target.startswith("<") and target.endswith(">"): target = target[1:-1] if scheme_pattern.match(target) or target.startswith("//"): continue path_text, separator, fragment = target.partition("#") destination = source if not path_text else source.parent / unquote(path_text) try: destination = destination.resolve() destination.relative_to(root) except ValueError: failures.append(f"{source.relative_to(root)}: escapes repository: {target}") continue if not destination.exists(): failures.append(f"{source.relative_to(root)}: missing target: {target}") continue if separator and destination.suffix.lower() == ".md": fragment = unquote(fragment).lower() if fragment not in anchors(destination): failures.append(f"{source.relative_to(root)}: missing anchor: {target}") if failures: print("\n".join(failures), file=sys.stderr) raise SystemExit(1) PY ``` ### Repository Hygiene And Review Reject an active Go workspace, tracked workspace files, a vendor tree, or a module replacement: ```sh case "$(go env GOWORK)" in ''|off) ;; *) printf '%s\n' 'an active Go workspace is not allowed' >&2; exit 1 ;; esac test -z "$(git ls-files go.work go.work.sum)" test ! -e vendor if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod then printf '%s\n' 'go.mod contains a replacement' >&2 exit 1 fi ``` Check whitespace in both unstaged and staged changes. List ignored files and scan tracked content for common credential forms: ```sh git diff --check git diff --cached --check test -z "$(git ls-files --others --ignored --exclude-standard)" credential_pattern='-----BEGIN ([A-Z0-9]+ )?PRIV''ATE KEY-----|AKI''A[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{36,}|sk-[A-Za-z0-9]{32,}' if git grep -nEI -e "$credential_pattern" -- . then printf '%s\n' 'possible credential found' >&2 exit 1 fi ``` Inspect `git status --short --untracked-files=all` and the complete diff before accepting a change. The status may contain only the intended source changes during development. Reject credentials, private keys, environment files, generated binaries, test or coverage output, downloaded assets, template residue, and any other artifact that does not belong in source control. The credential scan catches common forms but does not replace inspection of the actual change. After committing the accepted change, require a clean candidate: ```sh test -z "$(git status --porcelain)" ```