Files
promptkit/docs/roadmap/implementation.md

32 KiB

Effective Prompt Requirement Inspection Implementation Plan

Implement the feature roadmap in order. Each numbered stage is scoped for one Luna coding-agent prompt. This plan replaces the completed ReleaseKit adoption plan; that plan remains available in Git history.

Baseline And Working Rules

  • Baseline: 7827ca043b5de84a18199ac8f4b3056691aacbb5. There is no effective requirement-inspection API yet. InspectPrompt reports declarations; Prepare needs artifacts. The configured engine captures compiled templates.
  • The stages use the roadmap's recommended practical analysis core and active-context inventory. The previously open scope choices and alternatives are recorded under Open Questions; do not silently substitute broader analysis or an all-reference inventory.
  • Start with development, then read the agent, architecture, testing, and documentation policies. Reuse unchanged policies already read. Follow ADR 0001: interpret final effective configuration at the ConfigKit handoff, never raw/intermediate sources or configuration history.
  • Inspect the current worktree before each stage; preserve unrelated work. Validate and commit each completed unit under the agent policy. Keep current internal documentation accurate as each internal capability lands.
  • Preserve all existing inspection, construction, preparation, rendering, and execution contracts. Discovery limitations must not invalidate templates. Do not add variable defaults, input schemas, name restrictions, count/size limits, a branch-expansion budget, or generation-readiness checks.
  • No provider calls, actual publication, new dependency, CLI/HTTP layer, upstream ConfigKit change, or application-specific pipeline concept is needed.
  • Public declarations belong in the root facade; analysis belongs in internal/prompt, selection coordination in internal/usecase, and metadata capture in internal/configuration. Runtime/domain values remain independent of ConfigKit. Use the standard parser and template.IsTrue; do not implement a replacement template language or execute whole prompts with fake artifacts.

Concrete API And Result Decisions

Add root requirements.go in Stage 8. The method is:

func (e *Engine) InspectRequirements(
    ctx context.Context, req RequirementInspectionRequest,
) (*RequirementInspection, error)

Define ordinary root-owned values, mirrored by execution-owned internal values in internal/domain/requirements.go; do not export aliases to internal types:

type RequirementInspectionRequest struct {
    PromptID      string
    PromptVersion string
    ProfileID     string
    Vars          map[string]string
    SessionID     string
}

type RequirementKind string // constants: required, optional, conditional

type InputRequirement struct {
    Name        string
    Kind        RequirementKind
    Declared    bool
    ContentType *string
    Description *string
}

type VariableRequirement struct {
    Name     string
    Kind     RequirementKind
    Supplied bool
}

type RequirementIssueCode string

type RequirementIssue struct {
    Code         RequirementIssueCode
    InputName    string
    VariableName string
    Description  string
}

type RequirementInspection struct {
    PromptID      string
    PromptVersion string
    ProfileID     string
    Inputs        []InputRequirement
    Variables     []VariableRequirement
    Complete      bool
    Issues        []RequirementIssue
}

Use RequirementRequired, RequirementOptional, and RequirementConditional as the kind constant names. Use these issue constants:

Constant Value Meaning when discovery is affected
RequirementIssueMissingVariable missing_variable An absent caller value prevents resolving a dependency decision/name.
RequirementIssueInputContent input_content Unavailable artifact content affects further dependencies.
RequirementIssueDynamicInputName dynamic_input_name A helper's exact selected name remains unresolved.
RequirementIssueUnsupportedConstruct unsupported_construct A construct outside the analysis core prevents discovery.
RequirementIssueExpressionUnresolved expression_unresolved An expression cannot be evaluated within the supported semantics, including an incompatible operation.

Use safe, fixed-category descriptions, optionally identifying reported names. Do not expose the expression, its literals unrelated to identifiers, its evaluated values, raw template text, or a Go template execution error. An empty associated name means no particular named entry is identified; it does not establish that an input/variable is absent.

Classification, Metadata, And Ownership

  • Always report declarations. A declared required input stays required. Active template accesses can promote an optional declaration to required. Undeclared references on known inactive paths are omitted.
  • An entry is required when its use is established for the context, conditional when a possible use depends on unresolved analysis, and optional when no applicable use needs it and unresolved effects do not undermine that conclusion. An established required use dominates conditional/optional uses.
  • Aggregate inputs and variables by exact execution name. Do not trim request variable keys or discovered helper names. Configuration normalization remains with the existing configuration adapter.
  • Metadata pointers distinguish absent/cleared metadata from an explicit value, including an explicitly empty value. Declared distinguishes a known declaration from a helper-only reference. No new input-format enforcement.
  • Supplied is key presence in the caller's variable map, including an empty string. A required variable's presence satisfies that known presence requirement; a conditional variable's necessity is unresolved. Optional lookup absence is not a missing required value. Do not infer Boolean values, nonblank constraints, or other variable validation from names.
  • Sort inputs and variables lexicographically by exact name. Deduplicate issues by code/input-name/variable-name and sort by that tuple. Diagnostics need no public expression tree, node offsets, or JSON tags. No stable wire format or nil-versus-empty slice distinction is promised.
  • Return independent slices and metadata pointers; copy request variables at the facade like RunRequest. Never expose or mutate shared template trees. Callers retain the existing responsibility for concurrent mutation of requests.

Completeness And Uncertainty

Complete concerns discovery of both input and variable requirements, not whether they are satisfied. {{.audience}} {{input "note"}} can yield complete discovery with a missing required audience and known required note. Reading the body of note is unnecessary to identify that dependency.

Unknown values carry their cause internally. Emit issues and mark incomplete when uncertainty affects a dependency decision/name, or an opaque construct can hide requirements. Unknown output text alone does not cause incompleteness. An unknown branch containing only text need not make discovery incomplete.

For an unresolved branch, analyze both paths once and combine access summaries: a name definitely needed on both paths can stay required; names possible on only some paths are conditional. Merge branch-local unresolved issues. Compare requirement summaries, not rendered content, and do not enumerate combinations of independent conditions. Conservative incompleteness is valid where the core cannot prove an exact set; do not claim completeness there.

Unresolved dynamic names/opaque input effects must not leave otherwise optional declarations falsely definitive. Mark affected declared optional inputs conditional; an unrestricted unknown input name can affect all such declarations. An unknown-name issue also describes potential undiscovered undeclared names. Known required declarations remain required.

Initial Analysis Core

Analyze each selected template's executable root. Definitions that are never invoked do not themselves create requirements.

Construct Initial handling
Text/comments and sequences No text output; visit executable actions in order.
String, Boolean, nil literals; dot/root Track known values needed for dependencies; root is the supplied string map.
Root-map field access, including $.name Record a required named caller variable; absent key produces an unknown value, not an inspection error.
Dot inside with Follow the selected pipeline value; do not reinterpret string fields as caller root keys.
index on the root map with a known string key Record an optional named lookup, unless another use requires it; absent lookup yields the Go string zero value.
Local variables Single-variable declarations and assignments, root $, aliases, lexical scope, and pipeline results; locals are not caller variables.
input Known string names, including direct variable/alias-derived names; record the dependency and return an unknown body value.
Pipelines/parenthesized expressions Respect argument order and the previous value's final-argument position.
and, or, not Go truthiness and short-circuit access semantics; return Go-compatible operand/Boolean values.
eq, ne, lt, le, gt, ge String comparisons, including eq's multiple comparison operands; all ordinary arguments are evaluated as Go does.
if, else, with Known-path selection and unresolved-path joining, with correct dot/local scope.
Numeric operations, other built-ins, ranges, invoked named templates, other constructs outside the core Keep discovered dependencies and expose uncertainty when they affect discovery. Do not reject the definition or execute loops/calls speculatively.

A numeric literal or unsupported pure output expression is not itself a caller requirement. Discover its evaluated arguments where that is safe; its value can remain unknown until a later dependency decision needs it. Known short-circuit paths must not collect dependencies from skipped operands or branches.

Use a small private known/unknown value model. Branch joins retain identical known values and can retain finite possible string names from direct string-valued branch assignments. Different possible names are conditional; a missing variable/body or opaque value can introduce other unknown names. Do not evaluate cross-products of possible expression values or build a symbolic predicate system. Unsupported scopes must not invent root variable names: preserve explicit root accesses and literal helper references where identifiable, and report incompleteness for the rest.

Stage 1 — Retain Effective Declaration Metadata And Inspection Snapshots

Outcome: Inspection can obtain captured templates and metadata without changing execution definitions, hashes, or existing public inspection values.

Read: internal/configuration/load.go, prompts.go, internal/prompt/template.go, internal/domain/domain.go, internal/promptdef/hash.go, and the source document.

Work:

  1. Add internal metadata/snapshot values. Retain content-type/description presence alongside promptEntry, not inside the JSON-hashed domain.PromptDefinition or the existing public PromptInputDefinition.
  2. Capture effective per-input presence from Resolution.InputInfo during the existing Resolve callback before resolvePrompts can record normalized sequences. Use final prompt-ID/version/input-index paths only, following the existing backend/profile presence-capture pattern. Do not inspect history.
  3. Materialize metadata values from the normalized final declaration. Treat absent/null as no metadata and an explicit empty string as available metadata; preserve existing accepted/rejected configuration semantics.
  4. Define prompt.RequirementSnapshot containing compiled templates and declaration metadata, plus the narrow internal lookup GetRequirementSnapshot(id, version string) (RequirementSnapshot, bool). Implement it on the captured configuration adapter. Use exact selected keys and read-only template access; do not parse/reopen content during lookup.
  5. Keep the existing renderer's lookup and all old constructor/API paths intact. Update the internal source document for the implemented metadata capability.

Verify: Focused configuration tests cover absent, empty, nonempty, and normalization-sensitive metadata, with real final-value layering/resource capture. Confirm old inspection shape and definition hashes remain unchanged for equivalent existing definitions. Run go test ./internal/configuration ./internal/promptdef ./internal/prompt. Commit this unit; do not implement the public method yet.

Stage 2 — Add Internal Result Values And Literal Dependency Discovery

Outcome: Straight-line literal helper requirements and declarations are discoverable through an internal analyzer.

Work:

  1. Add internal counterparts of the result kinds, metadata, and issue values defined above in internal/domain/requirements.go.
  2. Add internal/prompt/requirements.go and focused tests. Provide one analyzer entry taking context, selected definition, captured snapshot, variables, and whether session-template analysis is bypassed; return internal analysis data. Selected public identities will be added by the runner later.
  3. Seed declarations, walk executable root actions, recognize literal input "name" calls, deduplicate names, and aggregate required accesses. Analyze the session first when applicable and messages in their existing order.
  4. Add aggregation/finalization helpers for classification, independent metadata, ordering, safe issue construction, and Complete. Do not produce prompt text.
  5. Until later stages implement control flow, return conservative incomplete results for constructs that can hide dependencies. Temporary partial coverage is internal implementation status, not a new public promise.
  6. Check context at ordinary traversal boundaries; return the context error, not a successful partial result after cancellation. Do not introduce workers, hard analysis limits, or stronger interruption promises.
  7. Describe implemented analysis ownership in the component/source documents.

Verify: Straight-line declared/undeclared references, duplicates, declared required unused inputs, optional unused declarations, literal body independence, empty input-free templates, and session-only references. Run go test ./internal/prompt ./internal/domain; commit.

Stage 3 — Resolve Caller Values, Map Lookups, And Local Aliases

Outcome: Caller variable requirements are distinct from input requirements; simple names and pipeline values can be tracked without rendering.

Work:

  1. Implement private value/environment records for known strings/Booleans/nil, root-map identity, local symbols, and unknown values with causes.
  2. Handle direct root field accesses and $.name, absent versus empty string, root-map index with known string keys, local declarations/assignments, and parenthesized/pipeline argument binding.
  3. Caller requirements arise from accesses to the root variable map, not local symbol names. Direct field access is required; optional index access is promoted if another active use requires that same caller key.
  4. Keep missing values unknown without aborting discovery. Do not mark incomplete solely because a text-substitution variable is absent.
  5. Preserve exact keys and values internally; return only requirement metadata and key-presence information. Add no coercion, trimming, or field-name limits.

Verify: Direct supplied/missing/empty values, optional lookups, duplicate access promotion, root versus local names, and pipeline argument position. Use small comparisons with real Go-template execution for presence behavior; do not duplicate the standard library's entire test matrix. Run go test ./internal/prompt; commit.

Stage 4 — Add Core Comparisons And Short-Circuit Semantics

Outcome: Common context expressions select values and dependencies consistently with Go templates.

Work:

  1. Implement not, string comparison families, and the operand-returning and/or behavior specified in the core table. Use template.IsTrue.
  2. Evaluate ordinary command arguments in order; eq's multiple comparison operands are not an or shortcut. Short-circuit only the appropriate and/or operands.
  3. Once a preceding operand leaves access uncertain, treat later potentially evaluated operands conditionally rather than definitely accessed or skipped. Preserve unknown causes for later dependency decisions.
  4. Keep unsupported/incompatible evaluation as an internal unknown value with a safe category. Do not leak values through errors or convert it into a new construction/request rejection.
  5. Inspect safe eager arguments of out-of-core built-ins for dependencies. Their output may remain unknown; never execute arbitrary functions or whole templates merely to determine a dependency.

Verify: Empty/nonempty strings, the truthiness of the string "false", comparisons, multi-operand eq, and and/or skips containing helper calls or missing variables. Compare representative supported expressions with the existing Go renderer. Run go test ./internal/prompt; commit.

Stage 5 — Analyze Known Branches And Lexical Scope

Outcome: Supplied context resolves ordinary if/with dependencies; known inactive uses are omitted.

Work:

  1. Add if/else and with traversal for known predicate values. Analyze only the selected path.
  2. Preserve root $, with dot changes, pipeline-declared symbols, block shadowing, and assignments to outer symbols. Follow Go's lexical lifetimes.
  3. Never reinterpret .field against a known string dot as a root-map lookup. Unsupported field operations have unknown values, not invented variables.
  4. Retain declarations regardless of branch activity; omit undeclared helper and variable accesses solely on a known inactive path.
  5. Keep uncertain predicates using the temporary conservative handling until Stage 6; do not guess their truth from placeholders.

Verify: Both known branches, nested if/with, root access from changed dot, declarations versus assignments/shadowing, and inactive helpers/missing variables. Include declared-required and declared-optional inputs in these cases. Run go test ./internal/prompt; commit.

Stage 6 — Join Unresolved Paths And Finalize Completeness

Outcome: Missing values and content-dependent branching return useful partial results without inventing definitive classifications.

Work:

  1. Analyze unknown branches once each in forked local environments. Join their requirement summaries and any assignments visible after the block. Preserve identical known values; retain direct possible string names where available.
  2. Required accesses shared definitely across both paths can remain required. Otherwise possible accesses are conditional. An unconditional use elsewhere or a declared required input still dominates.
  3. Emit uncertainty only when it affects discovered requirements. An unresolved text-only branch can still have a complete requirement set; no output rendering is needed to establish that.
  4. Support input-body-caused unknown conditions without reading the body: the predicate's known helper name is required; dependent branches are partial.
  5. Handle out-of-core controls/calls conservatively. Inspect identifiable literal helper and explicit root-variable accesses where scope is known. Do not follow recursive named calls, unroll ranges, or mark unrelated locals as caller variables. Opaque hidden requirements must keep the result incomplete.
  6. Ensure supported and unsupported analysis does not mutate compiled templates or change definition acceptance. No new parser/compiler validation rule.

Verify: Unknown branch unions and shared accesses, missing text variables with complete discovery, branch-sensitive missing variables, content predicates, unknown with dot, assignment joins, and valid range/named-template examples returning incomplete rather than errors. Run go test ./internal/prompt; commit.

Stage 7 — Finish Dynamic Names And Conservative Inventory Aggregation

Outcome: Direct variable/alias names resolve exactly; unresolved names preserve known candidates and correctly affect declaration classifications.

Work:

  1. Resolve input .input_name, root index results, aliases, parenthesized arguments, and piped names when the supported value is one known string. Supplied name values are used as identifiers, never normalized anew.
  2. For joined finite possible string names, report their possible input entries conditionally. For unrestricted unknown names, retain all already known entries and report undiscovered-name uncertainty.
  3. Emit dynamic-name issues together with useful missing-variable, input-content, unsupported, or expression-unresolved categories from the underlying value. Do not echo values beyond a resolved/known possible binding identifier.
  4. Make potentially targeted declared optional inputs conditional: candidate-only uncertainty affects matching declarations; unrestricted/opaque input effects can affect all optional declarations. Leave known required inputs required.
  5. Finalize deduplication, lexical ordering, metadata propagation, variable Supplied flags, and completeness. No placeholder "*" input name, invented variable default, payload read, or binding-validation API.

Verify: Exact dynamic names, empty supplied values versus missing values, aliases selected by known branches, finite names from unknown assignments, unrestricted names, repeated uses across messages/session, and optional declaration uncertainty. Verify results contain no unrelated variable values. Run go test ./internal/prompt; commit.

Stage 8 — Wire Selection And The Public Engine Facade

Outcome: A configured consumer can call the new operation without artifacts, credentials, schema preparation, or execution collaborators.

Work:

  1. Add internal/usecase/requirements_inspection.go and the internal request. Check nonblank prompt ID, normalize the direct session ID using the existing domain helper, and honor context cancellation before selection work.
  2. Use resolvePromptDefinition for exact version/default ambiguity behavior. Factor the existing request-profile/default-profile choice into a small shared private helper at its existing preparation location, retaining its error identities and preparation error ordering.
  3. Use resolveProfileSelection only to select identity. Do not call resolvePreparation, target-completeness checks, API-key validation, credential environment lookups, artifact/schema readers, renderer, validator, admission, generation, or observation/run-ID machinery.
  4. Obtain the captured requirement snapshot through the narrow lookup capability on the selected repository. A configured production engine always provides it. Missing internal capability/snapshot is an operational ErrPromptLoad, not successful incomplete discovery or a raw-template fallback.
  5. Analyze the messages and applicable session template; direct nonblank session bypass removes only that template's uses. Build the internal result with the selected identities and forward through internal/engine.
  6. Add the public values/method in root requirements.go, conversion helpers, GoDoc, and normal mapPublicError translation. Copy mutable values as above. Nil engine matches ErrInvalidConfig; operational errors return no partial result. Successful incomplete discovery returns a result and nil error.
  7. Preserve existing public error categories for absent prompt/profile, ambiguous versions, absent selected/default profile, invalid session ID, and cancellation. Add no new sentinel merely for uncertainty.
  8. Update internal component/runner documentation for the implemented method.

Verify: Focused runner tests with failing/counting execution collaborators prove selection-only behavior. Check explicit/default/partial profiles, exact selectors and ambiguity, session bypass, cancelled contexts, nil engine, and incomplete-success behavior. Run go test ./internal/usecase ./internal/engine ./... once for facade wiring; commit.

Stage 9 — Protect Public Semantics, Isolation, And Execution Parity

Outcome: Consumer-facing tests cover the acceptance outcomes without duplicating the analyzer's detailed case matrix.

Work:

  1. Add external-package requirements_inspection_contract_test.go using real configured engines and minimal synthetic definitions. Use the existing mapping/profile helpers, not a downstream checkout.
  2. Prove exact transcript discovery, undeclared literal inputs, required and optional declarations in inactive branches, context-selected names, and session-only requirements removed by a direct session ID.
  3. Cover complete-but-unsatisfied variable requirements, conditional/unknown dependencies, and empty complete versus empty incomplete input sets.
  4. Preserve classifiable errors for selection/cancellation. Test inspection against a valid partial profile and an explicit direct-key-required profile without an API key; missing runtime schemas and future artifact files must not prevent inspection. Inject fail-if-called artifact/model/validator collaborators where supported to prove the relevant absence of execution.
  5. Use a small parity table: known-context inspector requirements must agree with Prepare/the renderer's input and variable semantics when actual synthetic artifacts are later supplied. Do not use execution failure order as the definition of all discovered requirements.
  6. Test source capture using a file-backed message changed/deleted after engine construction. New inspection uses the captured content, like existing runs.
  7. Mutate returned metadata/slices and repeat inspection; run representative simultaneous inspections alongside preparation to verify no shared-state mutation. Test this ownership risk, not a new scheduling/order guarantee.
  8. Verify safe diagnostics/result formatting with marker values: unrelated variable values, direct session value, template text, and credential values are absent; input identifiers and declared descriptions are allowed.
  9. Keep analyzer tests as the owner of detailed expression/branch semantics; root tests own public values, errors, ownership, and assembled behavior.

Verify: Run focused new contract tests and affected package tests, with -race for the isolation tests. Reuse Stage 8 results for unchanged cases; commit. Avoid full-suite repetition solely to produce this commit.

Stage 10 — Document The Boundary And Verify The Assembled Feature

Outcome: Canonical documentation accurately describes the new capability, and the complete feature is ready for review, not automatically published.

Work:

  1. Finish GoDoc for every new public field, kind, issue category, method, ownership/error behavior, and supported discovery boundary. State that inspection is not generation readiness or future-byte validation.
  2. Add task-oriented consumer guidance comparing the three inspection methods and preparation. Demonstrate required transcript, missing variables, and explicit incomplete discovery without a Promptkit JSON format.
  3. Update relevant format cross-references, internal source/runner/component documents, and development reading guidance. Do not duplicate the exact public contract outside GoDoc or describe unsupported constructs as invalid.
  4. Extend the maintained preparation example with a small inspection use before actual artifact preparation, preserving all existing example output assertions. Verify the example's semantic inspection result locally without adding a second recurring release gate or replacing the other examples.
  5. Check every roadmap acceptance outcome against implementation/tests. Confirm the old inspection API/hash shapes and execution behavior are retained and no raw-source/history validation or new caller restriction was introduced.
  6. On final changed code, run one assembled offline verification using the selected local Go and prepared module caches: go test -race -vet=off -mod=readonly -count=1 ./..., go vet -mod=readonly ./..., and python3 scripts/check-examples.py. Set GOTOOLCHAIN=local, GOWORK=off, GOPROXY=off, GONOPROXY=none, and GOSUMDB=off. Check changed Go formatting, local documentation links, and git diff --check. There is no need to rerun release-tool publication fixtures for unchanged tools.
  7. Record verification and implemented status in the temporary roadmap/plan for user review. Do not tag, push, select a release version, write a prospective release note, or retire the review artifacts in this implementation stage. Release preparation owns those later decisions.
  8. Review the final change, commit the unit, and report remaining issues honestly. Do not mark unsupported constructs complete merely to make tests pass.

Completion Criteria

  • The root operation and dedicated public values implement the selected artifact-free, context-sensitive inspection contract.
  • Definitions/templates and required metadata presence come from the final captured configuration; current inspection/execution and hashes remain intact.
  • Practical-core complete results agree with execution semantics; uncertainty remains a structured successful partial result, not guessed absence or error.
  • Missing values, conditional requirements, metadata absence, and session bypass are distinguishable without leaking unrelated content.
  • Public classification and ownership tests pass; no provider/resource preflight, arbitrary limit, application wire schema, or new template-validity rule was added.
  • Canonical documentation and maintained examples reflect the implemented discovery boundary. Successful checks for the final snapshot are reported.

Open Questions

Confirm The Practical-Core Scope

The roadmap still records this as open. This plan uses the recommended core, made precise in the table above, and treats other dependency-relevant constructs as incomplete without affecting their execution validity.

Recommendation: Use that practical core for this feature. It addresses the consumer scenarios and gives maintainable, testable completeness while avoiding a full interpreter. Extend coverage later when concrete prompts justify it.

Alternative: Include ranges, invoked named templates, numeric comparison, and more built-in value pipelines in this implementation. That improves complete discovery for more prompts, but needs additional independent stages and more semantic parity coverage. It still cannot resolve artifact-content-dependent decisions before artifacts exist.

Confirm The Inactive-Reference Inventory

This plan uses the roadmap recommendation: declarations are always reported, while undeclared references solely on known inactive paths are omitted.

Recommendation: Keep that effective-context inventory. It avoids confusing potential future prompt uses with bindings the current context needs.

Alternative: Include statically identifiable inactive undeclared references as optional entries with an inactive-use explanation. That is useful for broader operator exploration, but expands result/issue semantics and requires tests and documentation distinguishing inventory from applicable dependencies.

These questions concern the two previously open scope choices, not a new publication or human sign-off gate. The stages above consistently implement the recommended baseline; revise that baseline if a different option is selected.