234 lines
8.3 KiB
Markdown
234 lines
8.3 KiB
Markdown
# 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)"
|
|
```
|