diff --git a/docs/specs/sf-prompt-modularization.md b/docs/specs/sf-prompt-modularization.md new file mode 100644 index 000000000..5432c5f9a --- /dev/null +++ b/docs/specs/sf-prompt-modularization.md @@ -0,0 +1,129 @@ +# SF Prompt Modularization + +Human-readable contract for the manifest-driven prompt context system. + +## Status + +M004 — Phase 3 complete. All builders migrated to `composeUnitContext` v2. + +## Overview + +Every autonomous unit type (plan-milestone, execute-task, deploy, …) declares +what context it needs in a **manifest**. The composer reads the manifest and +orchestrates artifact resolution, producing the `## Inlined Context` block that +each prompt template receives. + +This replaces the pre-Phase-3 approach where each builder hand-wired file reads +and DB queries inline. + +## Two composer APIs + +| API | Purpose | When to use | +|-----|---------|-------------| +| `composeInlinedContext(unitType, resolveArtifact)` | v1 — resolves `artifacts.inline` keys only | **Deprecated.** Retained in `unit-context-composer.js` for external consumers. Do not use in new builders. | +| `composeUnitContext(unitType, opts)` | v2 — resolves `prepend`, `inline`, `excerpt`, and `computed` artifacts | **Use for all new builders.** All in-tree builders now call this. | + +### `composeUnitContext` contract + +```ts +composeUnitContext(unitType: string, opts: { + base: string; // repo root + resolveArtifact?: (key: string) => Promise; + resolveExcerpt?: (key: string) => Promise; + computed?: Record string | null }>; +}) => Promise<{ prepend: string; inline: string }> +``` + +- **prepend** — computed blocks that should appear before the main inline content. +- **inline** — the joined context block suitable for `{{inlinedContext}}` substitution. +- Unknown `unitType` returns `{ prepend: "", inline: "" }` — callers fall through to default behavior. +- Missing resolvers or registry entries are skipped silently (no error). + +## Manifest schema + +```ts +interface UnitManifest { + skills: { mode: "all" | "planning" | string[] }; + knowledge: "full" | "scoped" | "critical-only" | "none"; + memory: "prompt-relevant" | "critical-only" | "none"; + codebaseMap: boolean; + preferences: "active-only" | "none"; + tools: string[]; + prepend?: string[]; // computed artifact ids (e.g. ["overrides"]) + artifacts: { + inline: string[]; // static artifact keys, resolved in order + excerpt: string[]; // excerpt artifact keys + onDemand: string[]; // referenced but not auto-inlined + computed?: string[]; // async computed artifact ids (e.g. ["knowledge", "graph"]) + }; + maxSystemPromptChars: number; +} +``` + +### Stable artifact keys (`ARTIFACT_KEYS`) + +Keys are **stable identifiers**, not paths. The resolver maps a key to its +source (file, DB row, computed block). + +Milestone-scoped: `roadmap`, `milestone-context`, `milestone-summary`, +`milestone-validation`, `milestone-research`, `milestone-plan` + +Slice-scoped: `slice-context`, `slice-research`, `slice-plan`, `slice-summary`, +`slice-summaries`, `slice-uat`, `slice-assessment` + +Task-scoped: `task-plan`, `task-summary`, `prior-task-summaries`, +`dependency-summaries`, `blocker-summaries` + +Project-scoped: `requirements`, `decisions`, `project`, `templates`, `queue` + +Validation-scoped: `verification-classes`, `outstanding-items`, `previous-validation` + +History-scoped: `prior-milestone-summary` + +## Migration decision matrix + +| Builder complexity | Recommendation | Example | +|-------------------|------------------|---------| +| Simple (1–3 static artifacts, no knowledge splice) | Full v2 via `composeUnitContext` | deploy, release, rollback, challenge | +| Medium (4–6 static artifacts, optional knowledge/graph) | v2 inline + imperative knowledge splice | research-milestone, complete-slice, reassess-roadmap | +| Complex (many artifacts, computed prepend, conditional logic) | v2 with full computed registry | plan-milestone, research-slice, plan-slice, replan-slice | +| Fully imperative (no composer call) | Keep imperative; add manifest for documentation | execute-task, reactive-execute | + +## Budget and caching + +- `resolvePromptBudgets()` computes context-window-scaled budgets once per +dispatch tick (1-second TTL). Do not add additional caches. +- `capPreamble()` caps inlined context at `min(30K chars, scaled inline budget)`. +- Exceeding `maxSystemPromptChars` logs a telemetry event; the composer does not +truncate. + +## Verification + +- `auto-prompts-phase3.test.mjs` — v2 contracts for plan-milestone, + replan-slice, validate-milestone, research-slice. +- `auto-prompts-complete-slice.test.mjs` — complete-slice closeout contract. +- `auto-prompts-v2-migration.test.mjs` — regression guard for all migrated + builders (research-milestone, run-uat, deploy, smoke-production, release, + rollback, challenge). +- `unit-context-manifest-computed.test.mjs` — manifest `computed` keys match + builder behavior. + +## Adding a new artifact key + +1. Add the key to `ARTIFACT_KEYS` in `unit-context-manifest.js`. +2. Add the key to the `inline` / `excerpt` / `onDemand` / `computed` list of + every manifest that needs it. +3. Implement the resolver in the builder's `resolveArtifact` function. +4. Add a regression test that asserts the artifact appears in the prompt. + +## Adding a new computed artifact type + +1. Define the builder function (e.g. `inlineKnowledgeBudgeted`). +2. Register it in the `computed` registry passed to `composeUnitContext`: + ```js + computed: { + knowledge: { build: (inputs, base) => inlineKnowledgeBudgeted(base, inputs.keywords) } + } + ``` +3. Add the id to `manifest.artifacts.computed`. +4. Add a test that asserts the computed block appears in the output.