Added a roadmap to address the issues identifed by the audit
This commit is contained in:
504
docs/roadmap/cleanup.md
Normal file
504
docs/roadmap/cleanup.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# Cleanup Implementation Roadmap
|
||||
|
||||
## Purpose
|
||||
|
||||
This roadmap turns the findings in [audit.md](audit.md) into a staged, decision-complete cleanup plan for Scriptorium.
|
||||
|
||||
Audience: LLM coding agents implementing the cleanup in order.
|
||||
|
||||
Controlling policies:
|
||||
|
||||
- [Documentation policy](../policy/documentation.md)
|
||||
- [Architecture policy](../policy/architecture.md)
|
||||
- [Development guide](../policy/development.md)
|
||||
|
||||
## Global Implementation Rules
|
||||
|
||||
- Implement stages in order.
|
||||
- Keep public CLI flags, HTTP request/response shapes, config precedence, prompt/profile ID semantics, and validation behavior stable unless a stage explicitly says otherwise.
|
||||
- Keep adapters thin and use-case policy in `internal/usecase`.
|
||||
- Prefer explicit helpers over reflection, generic workflow abstractions, or broad framework-style rewrites.
|
||||
- Update non-roadmap docs only when implemented behavior changes or when a stale implemented-behavior statement is found during a stage.
|
||||
- Do not document future behavior outside `docs/roadmap/`.
|
||||
- Run the stage-specific tests before moving to the next stage.
|
||||
- Run `go test ./...` after the final stage.
|
||||
|
||||
## Stage 1: Execution Target Mapping Cleanup
|
||||
|
||||
### Goal
|
||||
|
||||
Reduce drift when adding or changing execution/runtime fields such as `service_tier`, `api_key_env`, `reasoning_effort`, or future provider request keys.
|
||||
|
||||
### Scope
|
||||
|
||||
Update only explicit execution-target mapping and serialization paths. Do not add new CLI flags or new provider features.
|
||||
|
||||
### Implementation
|
||||
|
||||
In `internal/usecase`:
|
||||
|
||||
- Keep execution-target merge policy in `internal/usecase`.
|
||||
- Add focused helper coverage around `resolveExecutionTarget`, `mergeExecutionTarget`, and profile-to-target conversion.
|
||||
- Keep the existing semantics:
|
||||
- built-in defaults first;
|
||||
- profile values override defaults;
|
||||
- request overrides override profile values;
|
||||
- zero numeric values do not override;
|
||||
- empty/whitespace string values do not override;
|
||||
- non-empty `ExtraParams` replaces the previous map with a copy.
|
||||
|
||||
In `internal/adapter/http`:
|
||||
|
||||
- Add local helper functions:
|
||||
- `executionTargetFromModelOverrideDTO(*modelOverrideRequestDTO) *domain.ExecutionTarget`
|
||||
- `modelParamsDTOFromExecutionTarget(domain.ExecutionTarget) modelParamsDTO`
|
||||
- Use those helpers in `handler.go`.
|
||||
- Keep DTO types unexported and transport-owned.
|
||||
- Keep HTTP response field names and omission behavior unchanged.
|
||||
|
||||
In `internal/llm`:
|
||||
|
||||
- Add a local helper such as `openAIChatRequestFromGenerateRequest(req domain.GenerateRequest, defaultModel string) (openAIChatRequest, error)` or an equivalent small function.
|
||||
- Keep endpoint construction, HTTP client timeout handling, API-key environment lookup, and response parsing in `Generate`.
|
||||
- Keep outbound serialization behavior unchanged:
|
||||
- send `model` and `messages`;
|
||||
- send `temperature`, `max_tokens`, `top_p`, and `service_tier` only when currently sent;
|
||||
- send `response_format` only when structured output is present;
|
||||
- do not serialize `reasoning_effort` or `extra_params`.
|
||||
|
||||
Do not:
|
||||
|
||||
- use reflection to copy fields;
|
||||
- move HTTP DTOs into `internal/domain`;
|
||||
- add generic mapper packages;
|
||||
- change prepared-run JSON tags.
|
||||
|
||||
### Tests
|
||||
|
||||
Add or update tests so an all-fields `domain.ExecutionTarget` catches omissions.
|
||||
|
||||
Required tests:
|
||||
|
||||
- Runner merge/profile conversion:
|
||||
- profile values populate all supported execution fields;
|
||||
- runtime overrides beat profile values for all overrideable fields;
|
||||
- empty string overrides do not erase profile values;
|
||||
- empty `ExtraParams` does not erase profile values.
|
||||
- HTTP adapter:
|
||||
- request `model` object maps every supported field into `RunRequest.Execution`;
|
||||
- response `metadata.model_params` includes every supported field according to current DTO tags.
|
||||
- LLM client:
|
||||
- outbound JSON includes every serialized execution field;
|
||||
- outbound JSON omits `service_tier` when empty;
|
||||
- outbound JSON still omits `reasoning_effort` and `extra_params`.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/usecase ./internal/adapter/http ./internal/llm ./internal/format
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- No public behavior changes.
|
||||
- Adding a new execution target field later has obvious mapping/test locations.
|
||||
- Existing HTTP and LLM behavior remains stable.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 2: Prompt/Profile Filesystem Catalog Helper
|
||||
|
||||
### Goal
|
||||
|
||||
Centralize shared recursive YAML discovery mechanics while preserving prompt/profile-specific validation and error behavior.
|
||||
|
||||
### Scope
|
||||
|
||||
Create a narrow helper package for filesystem catalog mechanics only.
|
||||
|
||||
Recommended package:
|
||||
|
||||
- `internal/filecatalog`
|
||||
|
||||
### Implementation
|
||||
|
||||
Add helper functions with explicit, small responsibilities:
|
||||
|
||||
- recursively find YAML files under a root directory;
|
||||
- honor context cancellation during walking;
|
||||
- accept `.yaml` and `.yml`;
|
||||
- return stable sorted full paths;
|
||||
- compute clean relative paths from a root;
|
||||
- return filename stems with `.yaml`/`.yml` stripped.
|
||||
|
||||
Use the helper in:
|
||||
|
||||
- `internal/promptdef/filesystem_repository.go`
|
||||
- `internal/profile/filesystem_repository.go`
|
||||
|
||||
Preserve existing behavior:
|
||||
|
||||
- prompt/profile lookup uses YAML `id`, not file path;
|
||||
- subdirectories are organizational only;
|
||||
- duplicate prompt/profile IDs are invalid;
|
||||
- malformed likely-target files still surface errors;
|
||||
- relative nested paths still appear in errors;
|
||||
- prompt `content_file` resolution remains relative to the prompt YAML file;
|
||||
- prompt/profile strict YAML and validation stay in their existing packages.
|
||||
|
||||
Do not:
|
||||
|
||||
- create a generic repository framework;
|
||||
- merge prompt and profile normalization;
|
||||
- move prompt/profile domain policy into the helper;
|
||||
- change error messages except for unavoidable wording caused by helper extraction.
|
||||
|
||||
### Tests
|
||||
|
||||
Add tests for `internal/filecatalog`:
|
||||
|
||||
- nested YAML discovery;
|
||||
- `.yaml` and `.yml` accepted;
|
||||
- non-YAML files ignored;
|
||||
- returned paths sorted deterministically;
|
||||
- relative path formatting works for nested files;
|
||||
- filename stem stripping handles both extensions.
|
||||
|
||||
Keep existing prompt/profile repository tests passing.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/filecatalog ./internal/promptdef ./internal/profile
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Prompt/profile repository tests pass without behavior expectation changes.
|
||||
- Shared filesystem scanning logic exists in one place.
|
||||
- Prompt/profile packages still own their own validation and normalization.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 3: CLI Wiring And Settings Finalization Cleanup
|
||||
|
||||
### Goal
|
||||
|
||||
Reduce duplicated command setup while preserving each command's public flag surface and behavior.
|
||||
|
||||
### Scope
|
||||
|
||||
Clean up `internal/adapter/cli` only, except for tests.
|
||||
|
||||
### Implementation
|
||||
|
||||
Add CLI-local helpers. Recommended helpers:
|
||||
|
||||
- `commonCommandSettings` or similar struct containing resolved `promptDir`, `profileDir`, `schemaDir`, `serverAddr`, and `defaultRenderFormat` where applicable.
|
||||
- `resolveCommonSettings(fs *flag.FlagSet, configPath string, overrides appconfig.CLIOverrides) (commonCommandSettings, error)`.
|
||||
- `validateRequiredLibraryDirs(promptDir, profileDir string) error`.
|
||||
- `newRunner(promptDir, profileDir, schemaDir string, llmClient llm.Client) *usecase.Runner`.
|
||||
- Optionally `newOpenAIClient() (*llm.OpenAICompatibleClient, error)` if it removes exact duplication without obscuring command behavior.
|
||||
|
||||
Preserve command differences:
|
||||
|
||||
- `run` exposes runtime model override flags and `--schema-dir`;
|
||||
- `render` exposes runtime model override flags and `--format`, but not `--schema-dir`;
|
||||
- `serve` exposes `--addr` and `--schema-dir`, but no runtime model override flags;
|
||||
- `render` default format comes from `defaults.render_format` unless `--format` is set;
|
||||
- deprecated `--prompt-id` and `--profile-id` aliases remain accepted.
|
||||
|
||||
Keep existing parse functions:
|
||||
|
||||
- `parseRunArgs`
|
||||
- `parseRenderArgs`
|
||||
- `parseServeArgs`
|
||||
|
||||
Do not:
|
||||
|
||||
- replace the standard library `flag` package;
|
||||
- introduce a command framework;
|
||||
- make `serve` accept runtime model override flags;
|
||||
- change error prefixes such as `run parse error`, `render parse error`, or `serve parse error`;
|
||||
- change CLI output behavior.
|
||||
|
||||
### Tests
|
||||
|
||||
Required regression tests:
|
||||
|
||||
- `run`, `render`, and `serve` still apply config precedence correctly.
|
||||
- Missing effective `prompt_dir` and `profile_dir` still return the same guidance.
|
||||
- `render` still uses config default render format and explicit `--format` override.
|
||||
- `serve` still rejects runtime model override flags.
|
||||
- `run` and `render` still build equivalent runtime override requests for shared flags.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/adapter/cli
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- No CLI flag, output, exit-code, or precedence changes.
|
||||
- Runner dependency construction is centralized inside the CLI adapter.
|
||||
- Command-specific behavior remains easy to read.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 4: Stable Use-Case Error Reasons For HTTP Mapping
|
||||
|
||||
### Goal
|
||||
|
||||
Remove HTTP error mapping's dependency on runner error message substrings.
|
||||
|
||||
### Scope
|
||||
|
||||
Change error identity, not public HTTP error responses.
|
||||
|
||||
### Implementation
|
||||
|
||||
In `internal/usecase`:
|
||||
|
||||
- Add stable sentinel errors for invalid-request reasons that HTTP currently distinguishes by message text.
|
||||
- Required sentinels:
|
||||
- missing profile selection, for the case where neither request profile nor prompt `default_profile` is available;
|
||||
- missing API-key environment value, for the case where `api_key_env` is set but the named environment variable is unset or empty.
|
||||
- Wrap these sentinels with `ErrInvalidRequest` so existing broad invalid-request checks keep working.
|
||||
- Preserve clear human-readable runner errors.
|
||||
|
||||
In `internal/adapter/http`:
|
||||
|
||||
- Replace `strings.Contains(err.Error(), ...)` checks for these cases with `errors.Is`.
|
||||
- Keep current HTTP status codes, error codes, and response messages:
|
||||
- `400 profile_required`;
|
||||
- `400 api_key_env_missing`.
|
||||
|
||||
Do not:
|
||||
|
||||
- expose HTTP-specific error codes from `internal/usecase`;
|
||||
- change the HTTP JSON error body shape;
|
||||
- remove broad fallback handling for `usecase.ErrInvalidRequest`.
|
||||
|
||||
### Tests
|
||||
|
||||
Required tests:
|
||||
|
||||
- Runner tests assert `errors.Is(err, usecase.ErrProfileRequired)` or the chosen sentinel name for missing profile selection.
|
||||
- Runner tests assert `errors.Is(err, usecase.ErrAPIKeyEnvMissing)` or the chosen sentinel name for missing API-key environment value.
|
||||
- HTTP handler tests still assert unchanged status/code/message for both cases.
|
||||
- HTTP handler tests should not construct errors by relying on exact runner prose for these two cases.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/usecase ./internal/adapter/http
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- HTTP mapping no longer depends on runner message substrings for the two distinguished invalid-request cases.
|
||||
- Public HTTP behavior is unchanged.
|
||||
- Runner errors remain clear in CLI output.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 5: Schema Failure Regression Coverage
|
||||
|
||||
### Goal
|
||||
|
||||
Protect the current structured-output invariant before future schema cleanup: `Prepare` must load a `json_schema` document before any LLM call.
|
||||
|
||||
### Scope
|
||||
|
||||
Add regression coverage only. Do not add schema caching or change schema loading architecture in this stage.
|
||||
|
||||
### Implementation
|
||||
|
||||
In `internal/usecase/runner_test.go` or an appropriate package test:
|
||||
|
||||
- Add a test where a prompt uses `validation_mode: json_schema` with a missing or failing schema document.
|
||||
- Assert `Runner.Prepare` fails with `ErrValidation`.
|
||||
- Assert no LLM call is made for `Runner.Run` when structured-output schema loading fails.
|
||||
|
||||
If existing tests already cover part of this behavior, consolidate assertions without making the test suite harder to read.
|
||||
|
||||
Do not:
|
||||
|
||||
- cache compiled schemas;
|
||||
- change `validate.StandardValidator` behavior;
|
||||
- introduce a schema service abstraction.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/usecase ./internal/validate
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Missing structured-output schema fails before generation.
|
||||
- Existing JSON Schema validation behavior is unchanged.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 6: Test Fixture Cleanup
|
||||
|
||||
### Goal
|
||||
|
||||
Reduce repeated test setup after behavior-preserving production refactors are complete.
|
||||
|
||||
### Scope
|
||||
|
||||
Prefer package-local test helpers. Avoid cross-package test utility packages unless a helper is needed by more than two packages and represents a stable public fixture contract.
|
||||
|
||||
### Implementation
|
||||
|
||||
In `internal/adapter/cli/run_test.go`:
|
||||
|
||||
- Consolidate repeated temp prompt/profile/input setup into local helper functions.
|
||||
- Keep helper names behavior-focused, for example:
|
||||
- `newCLITestLibrary`
|
||||
- `writePromptFileWithDefaultProfile`
|
||||
- `writeProfileFile`
|
||||
- `runCLICommand`
|
||||
- Do not hide assertions inside helpers unless the assertion is truly setup validation.
|
||||
|
||||
In `internal/usecase/runner_test.go`:
|
||||
|
||||
- Keep existing fake interfaces package-local.
|
||||
- Remove only high-volume duplication that obscures test intent.
|
||||
|
||||
Do not:
|
||||
|
||||
- move package-private fake types into production code;
|
||||
- create a broad `internal/testutil` package unless a later cleanup stage proves it necessary;
|
||||
- rewrite tests into table-driven form when cases have meaningfully different setup.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/adapter/cli ./internal/usecase
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Test intent is at least as clear as before.
|
||||
- No production behavior changes.
|
||||
- Test fixture setup has less repeated boilerplate in CLI tests.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 7: Unsupported Placeholder Sweep
|
||||
|
||||
### Goal
|
||||
|
||||
Remove code that suggests unimplemented artifact behavior outside roadmap documentation.
|
||||
|
||||
### Scope
|
||||
|
||||
Remove unsupported placeholders only when they are not needed by current tests or public docs.
|
||||
|
||||
### Implementation
|
||||
|
||||
Remove `domain.ArtifactRefS3` from `internal/domain/domain.go` unless new evidence shows it is intentionally needed by implemented code.
|
||||
|
||||
Preserve current behavior:
|
||||
|
||||
- supported artifact reference types remain `inline` and `file`;
|
||||
- unsupported artifact reference types still return `artifact.ErrUnsupportedRefType`;
|
||||
- docs continue to describe only `inline` and `file` outside roadmap files.
|
||||
|
||||
Update tests only if they reference the removed constant. Prefer testing unsupported artifact behavior with a literal custom type such as `domain.ArtifactRefType("s3")` or `domain.ArtifactRefType("unsupported")`.
|
||||
|
||||
Do not:
|
||||
|
||||
- add S3 support;
|
||||
- document S3 as implemented;
|
||||
- add future-backend placeholders elsewhere.
|
||||
|
||||
### Validation
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/domain ./internal/artifact
|
||||
```
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Unsupported placeholder constant is removed.
|
||||
- Unsupported artifact-type behavior remains covered.
|
||||
- No non-roadmap doc claims unimplemented artifact support.
|
||||
- Stage is small enough for one implementation prompt.
|
||||
|
||||
## Stage 8: Final Verification And Documentation Alignment
|
||||
|
||||
### Goal
|
||||
|
||||
Confirm the cleanup sequence preserved behavior and documentation accuracy.
|
||||
|
||||
### Implementation
|
||||
|
||||
Run the full test suite:
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
Run the maintained render smoke command:
|
||||
|
||||
```bash
|
||||
go run ./cmd/scriptorium render --config ./examples/config.yml --prompt generic.markdown_summary --input transcript=./examples/fixtures/transcript.md --input glossary=./examples/fixtures/glossary.yml --format json
|
||||
```
|
||||
|
||||
Search for stale or unsupported terms:
|
||||
|
||||
```bash
|
||||
rg -n "ArtifactRefS3|s3|strings\\.Contains\\(err\\.Error\\(\\)|TODO|future|planned" internal docs README.md examples
|
||||
```
|
||||
|
||||
Review results manually:
|
||||
|
||||
- `s3` should not appear as an implemented artifact type.
|
||||
- `strings.Contains(err.Error())` should not be used for stable use-case reason mapping.
|
||||
- Any `future` or `planned` wording outside `docs/roadmap/` must describe current boundaries, not aspirational behavior.
|
||||
|
||||
Update docs only if cleanup changed implemented behavior or if the search reveals stale implemented-behavior docs.
|
||||
|
||||
### Acceptance Criteria
|
||||
|
||||
- Full test suite passes.
|
||||
- Maintained render smoke command succeeds.
|
||||
- No stale unsupported artifact placeholder remains.
|
||||
- Non-roadmap docs describe implemented behavior only.
|
||||
- Working tree contains only intentional cleanup changes.
|
||||
|
||||
## Deferred Work
|
||||
|
||||
Do not implement these during the staged cleanup unless a later audit makes them high-confidence:
|
||||
|
||||
- schema caching or a combined raw/compiled schema service;
|
||||
- artifact hash/build helper beyond a small helper introduced opportunistically during touched code;
|
||||
- generic YAML repository framework;
|
||||
- generic CLI command framework;
|
||||
- plugin architecture for future prompt/profile/schema/artifact backends;
|
||||
- durable state, manifests, checkpoints, or resume behavior.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
The cleanup roadmap is complete when all stages have been implemented in order, the final verification passes, and the resulting code still satisfies:
|
||||
|
||||
- `Runner.Run` reuses `Runner.Prepare`;
|
||||
- CLI and HTTP adapters instantiate `Runner` without a repairer;
|
||||
- unknown config/prompt/profile YAML and HTTP JSON fields are rejected;
|
||||
- raw API key values are not accepted or emitted;
|
||||
- prompt/profile subdirectories remain organizational only;
|
||||
- schema paths remain explicit and relative to `schema_dir` when not absolute;
|
||||
- public CLI and HTTP behavior remains stable.
|
||||
Reference in New Issue
Block a user