diff --git a/FEATURES.md b/FEATURES.md new file mode 100644 index 000000000..9c09379d3 --- /dev/null +++ b/FEATURES.md @@ -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. + + + +### 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` + + diff --git a/scripts/generate-features-inventory.mjs b/scripts/generate-features-inventory.mjs new file mode 100644 index 000000000..89ef516bb --- /dev/null +++ b/scripts/generate-features-inventory.mjs @@ -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 = ""; +export const 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(); +} diff --git a/src/tests/features-inventory-generator.test.ts b/src/tests/features-inventory-generator.test.ts new file mode 100644 index 000000000..31eeaa324 --- /dev/null +++ b/src/tests/features-inventory-generator.test.ts @@ -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/, + ); +});