feat: add FEATURES.md capability map and generator

Human-oriented documentation of SF capabilities, with a script that
keeps it in sync with workflow-tools.ts and extension manifests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-04-28 04:18:12 +02:00
parent 25797129e2
commit efd5e14e0a
3 changed files with 640 additions and 0 deletions

445
FEATURES.md Normal file
View file

@ -0,0 +1,445 @@
# FEATURES
This file is the human-oriented capability map for Singularity Foundry.
It is intentionally not the source of truth for schemas or tool parameters. Use it to answer:
- what SF can do today
- which surfaces are first-class versus experimental
- where a capability lives in the system
For exact contracts, use:
- `README.md` for product positioning and user docs
- `packages/mcp-server/src/workflow-tools.ts` for workflow tool schemas
- `src/resources/extensions/sf/` for planning/state-machine behavior
- `src/resources/extensions/*/extension-manifest.json` for extension inventory
- `packages/pi-ai/src/` for provider and model registry behavior
## Core Product Shape
SF is a coding-agent application built around:
- a milestone → slice → task planning hierarchy
- a DB-backed workflow state machine
- MCP-accessible workflow mutations and readers
- extension-based capability loading
- multi-provider model routing
- interactive and autonomous execution modes
The core planning/runtime loop is:
1. discuss / research / align
2. plan milestone
3. plan slice
4. execute task-by-task
5. verify gates
6. summarize and validate
7. reassess roadmap and continue
## Planning And Ceremony Capabilities
### Milestone planning
SF supports milestone plans with:
- milestone title, vision, and slice breakdown
- success criteria and definition of done
- key risks and proof strategy
- verification contract, integration, operational, and UAT sections
- requirement coverage and boundary-map support
### Vision meeting
Milestones can carry a weighted `visionMeeting` that captures:
- `pm`
- `userAdvocate`
- `customerPanel`
- `business`
- `researcher`
- `deliveryLead`
- `partner`
- `combatant`
- `architect`
- `moderator`
- weighted synthesis
- confidence by area
- recommended route: `discussing`, `researching`, or `planning`
This is the top-level roadmap/vision alignment ceremony.
### Slice planning
Slices support:
- goal
- success criteria
- proof level
- integration closure
- observability impact
- ordered task plans with expected files, verification, inputs, outputs
### Adversarial review
Slice planning supports first-class adversarial review with:
- `partner`
- `combatant`
- `architect`
This is treated as required planning structure, not commentary.
### Planning meeting
Slices also support a structured planning meeting with:
- trigger
- `pm`
- `researcher`
- `partner`
- `combatant`
- `architect`
- `moderator`
- recommended route
- confidence summary
This is the narrower execution-readiness ceremony.
### Replanning
When a blocker invalidates a slice plan, SF supports slice replanning with:
- blocker task + blocker description
- what changed
- updated tasks
- removed tasks
- updated slice-level planning fields
- updated adversarial review
- updated planning meeting
Replan state is preserved in DB and re-rendered into plan artifacts.
## Workflow State Machine
The SF workflow engine derives and enforces states including:
- `pre-planning`
- `needs-discussion`
- `planning`
- `evaluating-gates`
- `executing`
- `summarizing`
- `validating-milestone`
- `completing-milestone`
- `replanning-slice`
- `complete`
- `blocked`
Important properties:
- execution readiness is gated by artifact completeness, not just file existence
- meeting/ceremony data participates in readiness
- blocked/dependency-aware progression is built in
- routed-back plans stay in planning instead of pretending to be ready
## Artifact And Persistence Capabilities
SF persists workflow state in multiple synchronized forms:
- SQLite DB (`.sf/sf.db`)
- markdown planning artifacts
- state manifest snapshots
- worktree DB reconciliation state
- workflow events
Planning/ceremony state now survives across:
- DB writes
- markdown rendering
- pure projection rendering
- manifest export / restore
- worktree reconciliation
- state derivation and execution gating
- slice replanning
## Execution Capabilities
SF can execute work in:
- interactive mode
- headless mode
- auto mode
- parallel / multi-worker orchestration
Execution-related features include:
- task-sized dispatch units
- crash recovery and lock-aware state
- timeout supervision
- worktree isolation
- per-unit summaries and milestone completion flow
- roadmap reassessment after completed slices
## MCP And Workflow Tooling
The workflow layer is exposed over MCP, including mutation/read paths for:
- milestone planning
- slice planning
- slice replanning
- task completion
- slice completion
- milestone validation
- milestone completion
- roadmap reassessment
- gate results
- summary save/read flows
This makes SF usable from external clients without relying on slash-command prompt tricks.
## Search And Research Capabilities
SF has dedicated web/research support via onboarding, auth storage, and extension flows.
Currently supported first-class web-search providers include:
- `brave`
- `tavily`
- `serper`
- `exa`
Other search/research surfaces include:
- Ollama native web search / fetch integration
- Google search extension
- Context7 extension for library/documentation retrieval
- Jina-backed content extraction paths where configured
The search stack is available to automatic workflows, not only slash commands.
## Subagents And Background Work
SF includes subagent capabilities via the `subagent` extension, including:
- delegated agent runs
- background subagent jobs
- await/join behavior
- cancellation
- workflow-driven use rather than only interactive commands
This is useful for automatic coding flows and wave-based task execution.
## Extension Inventory
Bundled extension families currently include:
- `sf` — workflow engine, planning/state/artifacts
- `search-the-web`
- `subagent`
- `async-jobs`
- `bg-shell`
- `browser-tools`
- `context7`
- `genai-proxy`
- `google-search`
- `ollama`
- `remote-questions`
- `slash-commands`
- `mac-tools`
- `ttsr`
- `universal-config`
- `voice`
These are not all equal in product importance, but they are real shipped extension surfaces.
## Model And Provider Capabilities
SF supports multi-provider model routing across built-in and custom providers.
Notable supported/known providers in the current runtime and registry surface include:
- `anthropic`
- `anthropic-vertex`
- `openai`
- `azure-openai-responses`
- `openai-codex`
- `google`
- `google-gemini-cli`
- `google-vertex`
- `mistral`
- `amazon-bedrock`
- `ollama`
- `ollama-cloud`
- `openrouter`
- `groq`
- `xai`
- `github-copilot`
- `zai`
- `minimax`
- `minimax-cn`
- `kimi-coding`
- `xiaomi`
- `custom-openai`
Recent/custom provider support in this tree also includes:
- `zai` / GLM-family routing
- `xiaomi` / MiMo Anthropic-compatible endpoint
- `kimi-coding` / dedicated coding endpoint
- `minimax` Anthropic-compatible support
## Onboarding And Auth
Onboarding currently supports:
- LLM provider selection
- OAuth or API-key based provider setup where applicable
- local Ollama detection
- web-search provider setup
- remote questions setup
- tool-key collection for selected extensions
This is a real product capability, not just a doc path.
## Recovery, Reliability, And Operational Features
SF includes real operational hardening around:
- manifest bootstrapping and restore
- worktree/DB reconciliation
- cache invalidation around plan parsing
- atomic writes and TOCTOU protection
- gate-aware progression
- idle/timeout handling
- scoped recovery for auto mode
## UI And Interaction Surfaces
SF is not only a CLI. The repo also carries:
- TUI support
- web interface support
- VS Code extension support
- MCP server support
So the product surface is broader than “terminal prompt framework.”
## What This File Does Not Try To Be
This file does not list:
- every MCP tool parameter
- every extension command
- every model ID
- every preference flag
- every internal DB column
Those should stay close to code or generated inventories.
## Generated Inventory
The section below is generated from source declarations so this overview can stay concise while exact inventories remain refreshable.
<!-- GENERATED_FEATURE_INVENTORY_START -->
### Workflow Tools
Generated from `packages/mcp-server/src/workflow-tools.ts`.
- `sf_complete_milestone`
- `sf_complete_slice`
- `sf_complete_task`
- `sf_decision_save`
- `sf_generate_milestone_id`
- `sf_journal_query`
- `sf_milestone_complete`
- `sf_milestone_generate_id`
- `sf_milestone_status`
- `sf_milestone_validate`
- `sf_plan_milestone`
- `sf_plan_slice`
- `sf_plan_task`
- `sf_reassess_roadmap`
- `sf_replan_slice`
- `sf_requirement_save`
- `sf_requirement_update`
- `sf_roadmap_reassess`
- `sf_save_decision`
- `sf_save_gate_result`
- `sf_save_requirement`
- `sf_skip_slice`
- `sf_slice_complete`
- `sf_slice_replan`
- `sf_summary_save`
- `sf_task_complete`
- `sf_task_plan`
- `sf_update_requirement`
- `sf_validate_milestone`
### Bundled Extensions
Generated from `src/resources/extensions/*/extension-manifest.json`.
- `async-jobs` — [extension-manifest.json](src/resources/extensions/async-jobs/extension-manifest.json)
- `bg-shell` — [extension-manifest.json](src/resources/extensions/bg-shell/extension-manifest.json)
- `browser-tools` — [extension-manifest.json](src/resources/extensions/browser-tools/extension-manifest.json)
- `context7` — [extension-manifest.json](src/resources/extensions/context7/extension-manifest.json)
- `genai-proxy` — [extension-manifest.json](src/resources/extensions/genai-proxy/extension-manifest.json)
- `google-search` — [extension-manifest.json](src/resources/extensions/google-search/extension-manifest.json)
- `mac-tools` — [extension-manifest.json](src/resources/extensions/mac-tools/extension-manifest.json)
- `ollama` — [extension-manifest.json](src/resources/extensions/ollama/extension-manifest.json)
- `remote-questions` — [extension-manifest.json](src/resources/extensions/remote-questions/extension-manifest.json)
- `search-the-web` — [extension-manifest.json](src/resources/extensions/search-the-web/extension-manifest.json)
- `sf` — [extension-manifest.json](src/resources/extensions/sf/extension-manifest.json)
- `slash-commands` — [extension-manifest.json](src/resources/extensions/slash-commands/extension-manifest.json)
- `subagent` — [extension-manifest.json](src/resources/extensions/subagent/extension-manifest.json)
- `ttsr` — [extension-manifest.json](src/resources/extensions/ttsr/extension-manifest.json)
- `universal-config` — [extension-manifest.json](src/resources/extensions/universal-config/extension-manifest.json)
- `voice` — [extension-manifest.json](src/resources/extensions/voice/extension-manifest.json)
### Search Providers
Generated from the `search-the-web` extension provider declarations.
- `brave`
- `exa`
- `ollama`
- `serper`
- `tavily`
### Known Model Providers
Generated from `packages/pi-ai/src/types.ts` (`KnownProvider`).
- `alibaba-coding-plan`
- `alibaba-dashscope`
- `amazon-bedrock`
- `anthropic`
- `anthropic-vertex`
- `azure-openai-responses`
- `cerebras`
- `github-copilot`
- `google`
- `google-gemini-cli`
- `google-vertex`
- `groq`
- `huggingface`
- `kimi-coding`
- `longcat`
- `minimax`
- `minimax-cn`
- `mistral`
- `ollama`
- `ollama-cloud`
- `openai`
- `openai-codex`
- `opencode`
- `opencode-go`
- `openrouter`
- `vercel-ai-gateway`
- `xai`
- `xiaomi`
- `zai`
<!-- GENERATED_FEATURE_INVENTORY_END -->

View file

@ -0,0 +1,127 @@
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const repoRoot = resolve(__dirname, "..");
const featuresPath = join(repoRoot, "FEATURES.md");
const workflowToolsPath = join(repoRoot, "packages", "mcp-server", "src", "workflow-tools.ts");
const providersPath = join(repoRoot, "packages", "pi-ai", "src", "types.ts");
const extensionsRoot = join(repoRoot, "src", "resources", "extensions");
const searchToolPath = join(repoRoot, "src", "resources", "extensions", "search-the-web", "tool-search.ts");
export const START = "<!-- GENERATED_FEATURE_INVENTORY_START -->";
export const END = "<!-- GENERATED_FEATURE_INVENTORY_END -->";
function uniqueSorted(values) {
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
}
export function parseWorkflowToolNames() {
const src = readFileSync(workflowToolsPath, "utf8");
const matches = [...src.matchAll(/server\.tool\(\s*"([^"]+)"/g)].map((m) => m[1]);
return uniqueSorted(matches);
}
export function parseKnownProviders() {
const src = readFileSync(providersPath, "utf8");
const match = src.match(/export type KnownProvider =([\s\S]*?);/);
if (!match) throw new Error("Could not find KnownProvider in packages/pi-ai/src/types.ts");
const providers = [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
return uniqueSorted(providers);
}
export function parseBundledExtensions() {
const entries = readdirSync(extensionsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter((name) => {
try {
const manifestPath = join(extensionsRoot, name, "extension-manifest.json");
readFileSync(manifestPath, "utf8");
return true;
} catch {
return false;
}
});
return uniqueSorted(entries);
}
export function parseSearchProviders() {
const src = readFileSync(searchToolPath, "utf8");
const providers = [
...src.matchAll(/providers\.push\('([^']+)'\)/g),
...src.matchAll(/provider\?: '([^']+)'/g),
]
.map((m) => m[1])
.filter((p) => p !== "combosearch");
return uniqueSorted(providers);
}
function formatBullets(values, formatter = (value) => `- \`${value}\``) {
return values.map((value) => formatter(value)).join("\n");
}
export function buildSection() {
const workflowTools = parseWorkflowToolNames();
const extensions = parseBundledExtensions();
const searchProviders = parseSearchProviders();
const knownProviders = parseKnownProviders();
return [
"### Workflow Tools",
"",
"Generated from `packages/mcp-server/src/workflow-tools.ts`.",
"",
formatBullets(workflowTools),
"",
"### Bundled Extensions",
"",
"Generated from `src/resources/extensions/*/extension-manifest.json`.",
"",
formatBullets(
extensions,
(value) => `- \`${value}\` — [extension-manifest.json](${relative(repoRoot, join(extensionsRoot, value, "extension-manifest.json"))})`,
),
"",
"### Search Providers",
"",
"Generated from the `search-the-web` extension provider declarations.",
"",
formatBullets(searchProviders),
"",
"### Known Model Providers",
"",
"Generated from `packages/pi-ai/src/types.ts` (`KnownProvider`).",
"",
formatBullets(knownProviders),
"",
].join("\n");
}
export function updateFeaturesContent(features) {
const startIndex = features.indexOf(START);
const endIndex = features.indexOf(END);
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
throw new Error("FEATURES.md is missing generated inventory markers");
}
const before = features.slice(0, startIndex + START.length);
const after = features.slice(endIndex);
const section = `\n\n${buildSection()}`;
return `${before}${section}\n${after}`;
}
export function main() {
const features = readFileSync(featuresPath, "utf8");
const updated = updateFeaturesContent(features);
writeFileSync(featuresPath, updated);
process.stdout.write(`Updated ${relative(repoRoot, featuresPath)}\n`);
}
if (process.argv[1] && resolve(process.argv[1]) === __filename) {
main();
}

View file

@ -0,0 +1,68 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
START,
END,
buildSection,
parseBundledExtensions,
parseKnownProviders,
parseSearchProviders,
parseWorkflowToolNames,
updateFeaturesContent,
} from "../../scripts/generate-features-inventory.mjs";
test("features inventory generator surfaces expected workflow tool, extension, search, and provider inventories", () => {
const workflowTools = parseWorkflowToolNames();
const extensions = parseBundledExtensions();
const searchProviders = parseSearchProviders();
const knownProviders = parseKnownProviders();
assert.ok(workflowTools.includes("sf_plan_milestone"));
assert.ok(workflowTools.includes("sf_replan_slice"));
assert.ok(workflowTools.includes("sf_task_complete"));
assert.ok(extensions.includes("sf"));
assert.ok(extensions.includes("search-the-web"));
assert.ok(extensions.includes("subagent"));
assert.deepEqual(searchProviders, ["brave", "exa", "ollama", "serper", "tavily"]);
assert.ok(knownProviders.includes("mistral"));
assert.ok(knownProviders.includes("zai"));
assert.ok(knownProviders.includes("kimi-coding"));
assert.ok(knownProviders.includes("xiaomi"));
});
test("features inventory generator injects a rendered appendix between markers", () => {
const original = [
"# FEATURES",
"",
"Intro text.",
"",
START,
END,
"",
].join("\n");
const updated = updateFeaturesContent(original);
const generated = buildSection();
assert.match(updated, new RegExp(`${START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n\\n### Workflow Tools`));
assert.match(updated, /### Bundled Extensions/);
assert.match(updated, /### Search Providers/);
assert.match(updated, /### Known Model Providers/);
assert.match(updated, /- `sf_plan_milestone`/);
assert.match(updated, /- `search-the-web` — \[extension-manifest\.json]/);
assert.match(updated, /- `brave`/);
assert.match(updated, /- `xiaomi`/);
assert.ok(updated.includes(generated));
assert.ok(updated.includes(END));
});
test("features inventory generator fails closed when markers are missing", () => {
assert.throws(
() => updateFeaturesContent("# FEATURES\n\nNo markers here.\n"),
/FEATURES\.md is missing generated inventory markers/,
);
});