docs(specs): add sf-prompt-modularization.md operator guide

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Mikael Hugo 2026-05-15 19:47:20 +02:00
parent 92ff8186ba
commit a8a28bd7c0

View file

@ -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<string | null>;
resolveExcerpt?: (key: string) => Promise<string | null>;
computed?: Record<string, { build: (inputs, base) => 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 (13 static artifacts, no knowledge splice) | Full v2 via `composeUnitContext` | deploy, release, rollback, challenge |
| Medium (46 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.