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:
parent
25797129e2
commit
efd5e14e0a
3 changed files with 640 additions and 0 deletions
445
FEATURES.md
Normal file
445
FEATURES.md
Normal 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 -->
|
||||||
127
scripts/generate-features-inventory.mjs
Normal file
127
scripts/generate-features-inventory.mjs
Normal 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();
|
||||||
|
}
|
||||||
68
src/tests/features-inventory-generator.test.ts
Normal file
68
src/tests/features-inventory-generator.test.ts
Normal 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/,
|
||||||
|
);
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue