More self-explanatory names. No behavioral change — same files, same purpose. - .sf/TASTE.md → .sf/STYLE.md (# Taste → # Style) - .sf/ANTI-GOALS.md → .sf/NON-GOALS.md (# Anti-goals → # Non-goals) - All code references updated: auto-bootstrap-context, system-context, gitignore, milestone-framing-check, scaffold-constants, spec-projections - Section headings injected into agent context updated to match Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
256 lines
13 KiB
JavaScript
256 lines
13 KiB
JavaScript
/**
|
|
* spec-projections.js — docs/specs exports from SF working state.
|
|
*
|
|
* Purpose: export human-readable docs/specs files from the same SF working
|
|
* model agents use in `.sf`, so git history captures the contract without
|
|
* making docs a second source of truth.
|
|
*
|
|
* Consumer: commands-plan.js for `/plan specs generate|diff|check`.
|
|
*/
|
|
|
|
import { existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
|
|
const SPEC_GENERATOR_VERSION = "1";
|
|
|
|
function present(path) {
|
|
return existsSync(path) ? "present" : "missing";
|
|
}
|
|
|
|
function countTable(db, table) {
|
|
try {
|
|
return (
|
|
db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get()?.count ?? 0
|
|
);
|
|
} catch {
|
|
return "missing";
|
|
}
|
|
}
|
|
|
|
function readDbSummary(basePath) {
|
|
const dbPath = join(basePath, ".sf", "sf.db");
|
|
if (!existsSync(dbPath)) {
|
|
return ["- Database state: missing"].join("\n");
|
|
}
|
|
let db;
|
|
try {
|
|
db = new DatabaseSync(dbPath);
|
|
const version =
|
|
db.prepare("SELECT MAX(version) AS version FROM schema_version").get()
|
|
?.version ?? "unknown";
|
|
const milestones = countTable(db, "milestones");
|
|
const slices = countTable(db, "slices");
|
|
const tasks = countTable(db, "tasks");
|
|
const milestoneSpecs = countTable(db, "milestone_specs");
|
|
const sliceSpecs = countTable(db, "slice_specs");
|
|
const taskSpecs = countTable(db, "task_specs");
|
|
return [
|
|
`- Database schema version: ${version}`,
|
|
`- DB planning rows: milestones=${milestones}, slices=${slices}, tasks=${tasks}`,
|
|
`- DB spec rows: milestone_specs=${milestoneSpecs}, slice_specs=${sliceSpecs}, task_specs=${taskSpecs}`,
|
|
].join("\n");
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : String(err);
|
|
return `- Database state: unreadable (${message})`;
|
|
} finally {
|
|
db?.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build the source input block shared by generated spec exports.
|
|
*
|
|
* Purpose: make every generated spec disclose which `.sf` working model,
|
|
* database, and source inputs it was projected from so reviewers can audit
|
|
* drift.
|
|
*
|
|
* Consumer: generateOperatingModelSpec.
|
|
*/
|
|
export function buildWorkingModelInputs(basePath) {
|
|
const sfDir = join(basePath, ".sf");
|
|
return [
|
|
"## Working Model Inputs",
|
|
"",
|
|
`- Generator: \`sf-spec-projections@${SPEC_GENERATOR_VERSION}\``,
|
|
`- Database: \`.sf/sf.db\` (${present(join(sfDir, "sf.db"))})`,
|
|
`- Guidance: \`.sf/PRINCIPLES.md\` (${present(join(sfDir, "PRINCIPLES.md"))})`,
|
|
`- Guidance: \`.sf/STYLE.md\` (${present(join(sfDir, "STYLE.md"))})`,
|
|
`- Guidance: \`.sf/NON-GOALS.md\` (${present(join(sfDir, "NON-GOALS.md"))})`,
|
|
`- Optional knowledge: \`.sf/KNOWLEDGE.md\` (${present(join(sfDir, "KNOWLEDGE.md"))})`,
|
|
`- Optional preferences: \`.sf/preferences.yaml\` (${present(join(sfDir, "preferences.yaml"))})`,
|
|
readDbSummary(basePath),
|
|
"- Source roots analyzed as implementation evidence: `src/resources/extensions/sf/`, `src/headless*.ts`, `src/cli.ts`, `src/help-text.ts`, `web/`, `vscode-extension/`, `packages/`",
|
|
"",
|
|
"This file is a human export for review, navigation, and git history. Generated docs are allowed to change because Git keeps the human-facing history. If SF needs operational history or future-use knowledge, store it in `.sf`/DB-backed state instead of relying on this export.",
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
/**
|
|
* Generate the SF operating model human export.
|
|
*
|
|
* Purpose: provide one deterministic human-readable export of SF's terminology,
|
|
* state, and source-placement contract from the repo-local working model.
|
|
*
|
|
* Consumer: `/plan specs generate` and `/plan specs check`.
|
|
*/
|
|
export function generateOperatingModelSpec(basePath) {
|
|
return [
|
|
"# SF Operating Model",
|
|
"",
|
|
buildWorkingModelInputs(basePath).trimEnd(),
|
|
"",
|
|
"SF has one workflow engine and one database-first working model. TUI, CLI, web, editor integrations, and non-interactive automation must all drive the same flow:",
|
|
"",
|
|
"```text",
|
|
"intent -> structured state/evidence -> UOK/policy gates -> execution -> journal/evidence -> projected status",
|
|
"```",
|
|
"",
|
|
"The names below are separate axes. Do not use one as a synonym for another.",
|
|
"",
|
|
"## Flow",
|
|
"",
|
|
"The flow is the product behavior: how SF captures intent, plans work, applies policy, executes tasks, records evidence, and reports status. Flow behavior must not fork by UI surface. If a TUI run and a non-interactive run receive the same state, run control, and permission profile, they should follow the same control model.",
|
|
"",
|
|
"## Surface",
|
|
"",
|
|
"A surface is where a person or program drives or observes the flow.",
|
|
"",
|
|
"- **TUI surface** — interactive terminal UI.",
|
|
"- **CLI surface** — explicit commands and single-shot prompt entrypoints.",
|
|
"- **Web surface** — browser UI around the same state and flow.",
|
|
"- **Editor surface** — IDE/editor integration, usually through an adapter.",
|
|
"- **Machine surface** — non-interactive runner for CI, scripts, schedulers, and parent processes.",
|
|
"",
|
|
'`sf headless` is the current command name for the machine surface. It means "without the TUI"; it does not mean "JSON", "autonomous", or a separate flow.',
|
|
"",
|
|
"## Protocol",
|
|
"",
|
|
"A protocol is how a surface or adapter talks to SF or to another agent process.",
|
|
"",
|
|
"- **RPC** — SF's current child-process control channel.",
|
|
"- **stdio JSON-RPC** — process transport used by existing RPC-style adapters.",
|
|
"- **ACP** — editor/client protocol adapter for agent/editor interoperability.",
|
|
"- **HTTP/RPC** — possible daemon/web integration protocol.",
|
|
"- **Wire** — a low-level internal message layer, if introduced, below surfaces and adapters.",
|
|
"",
|
|
"Protocols are adapters around the flow. They should translate messages, not invent policy or planning semantics.",
|
|
"",
|
|
"## Output Format",
|
|
"",
|
|
"An output format is only the encoding of a response or event stream.",
|
|
"",
|
|
"- `text` — human-readable progress and results.",
|
|
"- `json` — one machine-readable result object.",
|
|
"- `stream-json` / JSONL — event stream for parent processes and monitors.",
|
|
"",
|
|
"JSON is not a surface, run control, or permission profile. It is one output format that the machine surface can emit.",
|
|
"",
|
|
"## Run Control",
|
|
"",
|
|
"Run control describes how far SF continues through the flow before stopping for the operator.",
|
|
"",
|
|
"- **manual** — user approves each consequential step.",
|
|
"- **assisted** — SF proposes and executes bounded steps, with human approval for important or uncertain actions.",
|
|
"- **autonomous** — SF continues through the flow until policy, evidence, budget, or completion stops it.",
|
|
"",
|
|
"`auto` is not a run-control mode. Use **autonomous** for continuous run control; use **assisted** for bounded human-guided progression.",
|
|
"",
|
|
"> Competitor note: Copilot CLI calls continuous run control autopilot.",
|
|
"> SF does not use that product name. The SF term is autonomous mode,",
|
|
"> and it stays separate from permission profiles, surfaces, protocols,",
|
|
"> and output formats.",
|
|
"",
|
|
"UOK kernel records carry `runControl` as a first-class lifecycle field. Workflow phases such as planning, building, verification, and finalization are separate execution stages, not run-control modes.",
|
|
"",
|
|
"## Permission Profile",
|
|
"",
|
|
"A permission profile describes what SF is allowed to touch when a run-control mode asks it to act.",
|
|
"",
|
|
"- **restricted** — read-mostly or planning-artifact-only permissions.",
|
|
"- **normal** — default workspace permissions for ordinary project work.",
|
|
"- **trusted** — wider local permissions for a trusted operator/workspace.",
|
|
"- **unrestricted** — explicit danger profile; use for logs, policy records, or deliberate bypass flows, not as friendly default product language.",
|
|
"",
|
|
"Run control and permission profile are independent. For example, `autonomous + restricted` can keep going with narrow permissions, while `manual + trusted` still asks before each consequential step but can perform broader approved actions.",
|
|
"",
|
|
"UOK kernel records and execution-policy decisions carry `permissionProfile` as the trust posture. Permission expansion never implies autonomous continuation.",
|
|
"",
|
|
"## Naming Rules",
|
|
"",
|
|
"- Say **flow** for the shared planning/execution engine.",
|
|
"- Say **surface** for TUI, CLI, web, editor, or machine entrypoints.",
|
|
"- Say **protocol** for ACP, RPC, stdio JSON-RPC, HTTP, or wire messages.",
|
|
"- Say **output format** for `text`, `json`, and `stream-json`.",
|
|
"- Say **run control** for `manual`, `assisted`, and `autonomous`.",
|
|
"- Say **permission profile** for `restricted`, `normal`, `trusted`, and `unrestricted`.",
|
|
"- Use **headless** only for the current `sf headless` command and implementation path. Product docs should explain it as the machine surface.",
|
|
"",
|
|
"## Working State Contract",
|
|
"",
|
|
"SF working state is database-first. An initialized SF repo has `.sf/sf.db`, and runtime tools use it as the canonical structured store for planning hierarchy, ordering, gates, ledgers, schedules, and validation-sensitive state.",
|
|
"",
|
|
"Markdown under `.sf/` has two roles:",
|
|
"",
|
|
"- working guidance and knowledge that the runtime loads, such as `PRINCIPLES.md`, `STYLE.md`, `NON-GOALS.md`, `KNOWLEDGE.md`, and `preferences.yaml`;",
|
|
"- human-readable projections from DB-owned records, such as rendered decisions, requirements, roadmap, plan, summary, and state files.",
|
|
"",
|
|
"Markdown under `docs/specs/` is a human export for review, navigation, and git history. Generated docs can change; Git records that human-facing history. If SF needs its own operational history, it should store that in `.sf`/DB-backed state. Plans should record any surface, protocol, output-format, run-control, or permission-profile impact explicitly when a milestone changes integration behavior.",
|
|
"",
|
|
"## Source Placement",
|
|
"",
|
|
"SF source placement follows the same axis model. New code should extend the owning axis instead of creating parallel trees.",
|
|
"",
|
|
"### Core Flow",
|
|
"",
|
|
"- `src/resources/extensions/sf/` owns the SF workflow extension: planning tools, UOK/runtime state, `/next` commands, prompts, templates, doctors, schedule, and DB-backed state.",
|
|
"- `src/resources/extensions/` owns bundled extension packages loaded into the runtime.",
|
|
"- `src/resources/agents/`, `src/resources/skills/`, and `src/resources/workflows/` own bundled runtime resources, not independent product flows.",
|
|
"",
|
|
"### Surfaces",
|
|
"",
|
|
"- `src/cli.ts` and `src/help-text.ts` own CLI/session entrypoint behavior and command help.",
|
|
"- `src/headless*.ts` owns the existing `sf headless` machine-surface command path. Keep the command name; describe it as the machine surface in product language.",
|
|
"- `web/` owns the browser surface.",
|
|
"- `vscode-extension/` owns the editor surface.",
|
|
"- `packages/tui/` owns reusable TUI primitives and terminal UI components.",
|
|
"",
|
|
"### Protocols And Adapters",
|
|
"",
|
|
"- `packages/rpc-client/` owns reusable RPC client protocol code.",
|
|
"- RPC child-process orchestration stays in the machine-surface path unless promoted into a reusable protocol package.",
|
|
"- ACP, stdio JSON-RPC, HTTP, and future wire layers are protocol/adapters. They should translate messages to the same SF flow, not fork planning semantics.",
|
|
"",
|
|
"### Workspace Packages",
|
|
"",
|
|
"- `packages/agent-core/` owns reusable agent-core primitives.",
|
|
"- `packages/ai/` owns provider/model integration.",
|
|
"- `packages/coding-agent/` owns reusable coding-agent substrate inherited from Pi.",
|
|
"- `packages/daemon/` owns daemonized background service code.",
|
|
"- `packages/native/` and `rust-engine/` own native/Rust performance paths.",
|
|
"",
|
|
"### State And Projections",
|
|
"",
|
|
"- `.sf/sf.db` is the canonical structured runtime state store for initialized SF repos. Treat a missing or unreadable DB as bootstrap/recovery, not a normal alternate source of truth.",
|
|
"- `.sf/DECISIONS.md`, `.sf/REQUIREMENTS.md`, milestone roadmaps, and similar files are rendered working projections when database-backed tools own the data. They are useful to humans and agents but must not compete with DB rows.",
|
|
"- `.sf/PRINCIPLES.md`, `.sf/STYLE.md`, `.sf/NON-GOALS.md`, `.sf/KNOWLEDGE.md`, and `.sf/preferences.yaml` are repo-local working guidance files when present.",
|
|
"- Generated `.sf/` runtime files are evidence, projections, or import/recovery artifacts.",
|
|
"- Durable human-facing exports belong in `docs/specs/`, `docs/adr/`, or `docs/plans/`. They are reviewable projections and git-history artifacts, not a second planning database.",
|
|
"",
|
|
"### Placement Rules",
|
|
"",
|
|
"- Do not create a second implementation because a feature appears in another surface. Add an adapter to the same flow.",
|
|
"- Do not name output encodings as surfaces. JSON belongs to output formats.",
|
|
"- Do not name permission expansion as run control. `autonomous` means the loop continues; `trusted` or `unrestricted` means the permission profile widened.",
|
|
"- Do not route human questions because of `headless`. Questions come from run-control and permission-policy gates; the surface only determines delivery.",
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
export const PROMOTED_SPEC_PROJECTIONS = [
|
|
{
|
|
path: "docs/specs/sf-operating-model.md",
|
|
generate: generateOperatingModelSpec,
|
|
},
|
|
];
|