refactor(sf): rename guidance files TASTE.md→STYLE.md, ANTI-GOALS.md→NON-GOALS.md

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>
This commit is contained in:
Mikael Hugo 2026-05-10 21:28:31 +02:00
parent 48a01dd764
commit 702ec3fc0e
8 changed files with 31 additions and 31 deletions

View file

@ -1,4 +1,4 @@
# Anti-goals # Non-goals
- SF must not ship or revive an MCP server package or runtime endpoint. SF may consume external MCP servers as a client, but its own tools remain native SF/pi tools. - SF must not ship or revive an MCP server package or runtime endpoint. SF may consume external MCP servers as a client, but its own tools remain native SF/pi tools.
- Runtime state files under `.sf/` must not become a peer source of truth when SQLite can hold the structured state. JSON, JSONL, and Markdown runtime artifacts are generated evidence, projections, or legacy import inputs. - Runtime state files under `.sf/` must not become a peer source of truth when SQLite can hold the structured state. JSON, JSONL, and Markdown runtime artifacts are generated evidence, projections, or legacy import inputs.

View file

@ -1,4 +1,4 @@
# Taste # Style
- Prefer runtime adapters over ad hoc file parsing when reading SF state. For example, query solver eval history through `sf-db.js` helpers rather than reading `.sf/evals/**/report.json`. - Prefer runtime adapters over ad hoc file parsing when reading SF state. For example, query solver eval history through `sf-db.js` helpers rather than reading `.sf/evals/**/report.json`.
- Make DB-backed tools the pleasant path. If a human-readable file mirrors structured state, prefer a tool that mutates the DB and regenerates the file over hand-editing the projection. - Make DB-backed tools the pleasant path. If a human-readable file mirrors structured state, prefer a tool that mutates the DB and regenerates the file over hand-editing the projection.

View file

@ -21,9 +21,9 @@ const AUTO_BOOTSTRAP_SF_SPEC_FILES = [
".sf/RUNTIME.md", ".sf/RUNTIME.md",
".sf/STATE.md", ".sf/STATE.md",
".sf/PRINCIPLES.md", ".sf/PRINCIPLES.md",
".sf/TASTE.md", ".sf/STYLE.md",
".sf/preferences.yaml", ".sf/preferences.yaml",
".sf/ANTI-GOALS.md", ".sf/NON-GOALS.md",
".sf/CODEBASE.md", ".sf/CODEBASE.md",
]; ];
const AUTO_BOOTSTRAP_ORIENTATION_FILES = [ const AUTO_BOOTSTRAP_ORIENTATION_FILES = [
@ -117,7 +117,7 @@ export function buildAutoBootstrapContext(basePath) {
"Use explorer-style subagents or equivalent high-context research passes before planning when the runtime supports them.", "Use explorer-style subagents or equivalent high-context research passes before planning when the runtime supports them.",
"Recommended explorer passes: docs/purpose/vision; source architecture and dependency map; tests/gates/tooling; risks/backlog/eval candidates.", "Recommended explorer passes: docs/purpose/vision; source architecture and dependency map; tests/gates/tooling; risks/backlog/eval candidates.",
"Merge explorer findings into one repo map with cited file paths before creating milestones.", "Merge explorer findings into one repo map with cited file paths before creating milestones.",
"Follow harness-engineering principles: keep AGENTS.md short as a table of contents, use docs/ for human exports and git-history reports, capture operational knowledge in .sf/DB-backed state, prefer mechanically enforced architecture/taste rules, and add cleanup/gardening work when repo knowledge is stale.", "Follow harness-engineering principles: keep AGENTS.md short as a table of contents, use docs/ for human exports and git-history reports, capture operational knowledge in .sf/DB-backed state, prefer mechanically enforced architecture/style rules, and add cleanup/gardening work when repo knowledge is stale.",
"Optimize for agent legibility: every milestone should improve the next agent's ability to understand, validate, and safely modify the repo.", "Optimize for agent legibility: every milestone should improve the next agent's ability to understand, validate, and safely modify the repo.",
"Create actionable milestones and slices from the repo's docs and source tree rather than asking the user to restate them.", "Create actionable milestones and slices from the repo's docs and source tree rather than asking the user to restate them.",
"", "",

View file

@ -446,7 +446,7 @@ function loadSelfFeedbackBlock(cwd) {
return `\n\n[SELF-FEEDBACK — Recent sf-internal anomalies]\n\n${block}`; return `\n\n[SELF-FEEDBACK — Recent sf-internal anomalies]\n\n${block}`;
} }
/** /**
* Load tacit knowledge files (.sf/PRINCIPLES.md, .sf/TASTE.md, .sf/ANTI-GOALS.md) * Load tacit knowledge files (.sf/PRINCIPLES.md, .sf/STYLE.md, .sf/NON-GOALS.md)
* into a single block injected after the architecture block. * into a single block injected after the architecture block.
* *
* Each section is capped at 4 KB. Sections are skipped silently when the * Each section is capped at 4 KB. Sections are skipped silently when the
@ -475,13 +475,13 @@ export function loadTacitKnowledgeBlock(cwd) {
return stripped; return stripped;
} }
const principles = readSection("PRINCIPLES.md"); const principles = readSection("PRINCIPLES.md");
const taste = readSection("TASTE.md"); const style = readSection("STYLE.md");
const antiGoals = readSection("ANTI-GOALS.md"); const nonGoals = readSection("NON-GOALS.md");
if (!principles && !taste && !antiGoals) return ""; if (!principles && !style && !nonGoals) return "";
const parts = ["[TACIT KNOWLEDGE — read carefully]"]; const parts = ["[TACIT KNOWLEDGE — read carefully]"];
if (principles) parts.push(`\n## Principles\n\n${principles}`); if (principles) parts.push(`\n## Principles\n\n${principles}`);
if (taste) parts.push(`\n## Taste\n\n${taste}`); if (style) parts.push(`\n## Style\n\n${style}`);
if (antiGoals) parts.push(`\n## Anti-goals\n\n${antiGoals}`); if (nonGoals) parts.push(`\n## Non-goals\n\n${nonGoals}`);
return `\n\n${parts.join("\n")}`; return `\n\n${parts.join("\n")}`;
} }
/** /**

View file

@ -20,7 +20,7 @@ export { SF_RUNTIME_PATTERNS } from "./git-runtime-patterns.js";
/** /**
* SF runtime/generated exclusion patterns for repos where .sf/ is a LOCAL DIRECTORY. * SF runtime/generated exclusion patterns for repos where .sf/ is a LOCAL DIRECTORY.
* Granular so deliberate human-authored guidance such as .sf/PRINCIPLES.md, * Granular so deliberate human-authored guidance such as .sf/PRINCIPLES.md,
* .sf/TASTE.md, and .sf/ANTI-GOALS.md can remain trackable. * .sf/STYLE.md, and .sf/NON-GOALS.md can remain trackable.
* *
* NOT used when .sf/ is a symlink symlinks need the blanket SF_SYMLINK_EXCLUSION_PATTERNS * NOT used when .sf/ is a symlink symlinks need the blanket SF_SYMLINK_EXCLUSION_PATTERNS
* because git cannot traverse symlinks to match per-file patterns. * because git cannot traverse symlinks to match per-file patterns.

View file

@ -3,7 +3,7 @@
* *
* Reviews: * Reviews:
* 1. Milestone CONTEXT.md / title against PROJECT.md vision * 1. Milestone CONTEXT.md / title against PROJECT.md vision
* 2. .sf/ANTI-GOALS.md does this milestone violate any? * 2. .sf/NON-GOALS.md does this milestone violate any?
* 3. Category error: is this solving the right problem? * 3. Category error: is this solving the right problem?
* *
* Non-blocking: findings are surfaced as structured annotations. * Non-blocking: findings are surfaced as structured annotations.
@ -13,11 +13,11 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { sfRoot } from "./paths.js"; import { sfRoot } from "./paths.js";
/** /**
* Check milestone framing against project vision, anti-goals, and category-error heuristics. * Check milestone framing against project vision, non-goals, and category-error heuristics.
* *
* Reads: * Reads:
* - <basePath>/PROJECT.md (vision) * - <basePath>/PROJECT.md (vision)
* - <basePath>/.sf/ANTI-GOALS.md * - <basePath>/.sf/NON-GOALS.md
* - <basePath>/.sf/milestones/<milestoneId>/<milestoneId>-CONTEXT.md (or CONTEXT.md) * - <basePath>/.sf/milestones/<milestoneId>/<milestoneId>-CONTEXT.md (or CONTEXT.md)
* *
* @param basePath - project root (cwd) * @param basePath - project root (cwd)
@ -30,8 +30,8 @@ export function checkMilestoneFraming(basePath, milestoneId) {
const projectMdPath = join(basePath, "PROJECT.md"); const projectMdPath = join(basePath, "PROJECT.md");
const projectMd = safeRead(projectMdPath); const projectMd = safeRead(projectMdPath);
const sfDir = sfRoot(basePath); const sfDir = sfRoot(basePath);
const antiGoalsPath = join(sfDir, "ANTI-GOALS.md"); const nonGoalsPath = join(sfDir, "NON-GOALS.md");
const antiGoalsMd = safeRead(antiGoalsPath); const nonGoalsMd = safeRead(nonGoalsPath);
// Try to find milestone context file // Try to find milestone context file
const milestonePath = join(sfDir, "milestones", milestoneId); const milestonePath = join(sfDir, "milestones", milestoneId);
const contextCandidates = [ const contextCandidates = [
@ -49,14 +49,14 @@ export function checkMilestoneFraming(basePath, milestoneId) {
if (!contextMd) return findings; // nothing to check if (!contextMd) return findings; // nothing to check
const contextLower = contextMd.toLowerCase(); const contextLower = contextMd.toLowerCase();
// ── Anti-goal keyword check ────────────────────────────────────────────── // ── Anti-goal keyword check ──────────────────────────────────────────────
if (antiGoalsMd) { if (nonGoalsMd) {
const antiGoalLines = extractBulletLines(antiGoalsMd); const nonGoalLines = extractBulletLines(nonGoalsMd);
for (const line of antiGoalLines) { for (const line of nonGoalLines) {
const keywords = extractKeywords(line); const keywords = extractKeywords(line);
const matched = keywords.filter((kw) => contextLower.includes(kw)); const matched = keywords.filter((kw) => contextLower.includes(kw));
if (matched.length > 0) { if (matched.length > 0) {
findings.push({ findings.push({
concern: `Milestone description contains "${matched[0]}" — ANTI-GOALS.md entry: "${line.slice(0, 120)}"`, concern: `Milestone description contains "${matched[0]}" — NON-GOALS.md entry: "${line.slice(0, 120)}"`,
source: "anti_goal", source: "anti_goal",
severity: "warning", severity: "warning",
}); });
@ -144,7 +144,7 @@ function extractBulletLines(text) {
} }
/** /**
* Extract significant lowercase keywords (length >= 5, non-common) from text. * Extract significant lowercase keywords (length >= 5, non-common) from text.
* Used for fuzzy matching between milestone context and anti-goals / vision. * Used for fuzzy matching between milestone context and non-goals / vision.
*/ */
function extractKeywords(text) { function extractKeywords(text) {
const stopwords = new Set([ const stopwords = new Set([

View file

@ -434,8 +434,8 @@ Add entries as you make decisions. Each entry: 1-2 sentences. Cite the rationale
`, `,
}, },
{ {
path: ".sf/TASTE.md", path: ".sf/STYLE.md",
content: `# Taste content: `# Style
What good code looks like here. Idioms, conventions, "we prefer X over Y" calls. What good code looks like here. Idioms, conventions, "we prefer X over Y" calls.
@ -447,10 +447,10 @@ Add entries as you notice patterns worth preserving. Each entry: 1-2 sentences w
`, `,
}, },
{ {
path: ".sf/ANTI-GOALS.md", path: ".sf/NON-GOALS.md",
content: `# Anti-goals content: `# Non-goals
What we explicitly DON'T want. Things that look attractive but we've decided against. What this project explicitly does not want. Things that look attractive but have been decided against.
This is gold most wrong agent calls come from not knowing what to avoid. Each entry: 1-2 sentences with the rationale. This is gold most wrong agent calls come from not knowing what to avoid. Each entry: 1-2 sentences with the rationale.

View file

@ -75,8 +75,8 @@ export function buildWorkingModelInputs(basePath) {
`- Generator: \`sf-spec-projections@${SPEC_GENERATOR_VERSION}\``, `- Generator: \`sf-spec-projections@${SPEC_GENERATOR_VERSION}\``,
`- Database: \`.sf/sf.db\` (${present(join(sfDir, "sf.db"))})`, `- Database: \`.sf/sf.db\` (${present(join(sfDir, "sf.db"))})`,
`- Guidance: \`.sf/PRINCIPLES.md\` (${present(join(sfDir, "PRINCIPLES.md"))})`, `- Guidance: \`.sf/PRINCIPLES.md\` (${present(join(sfDir, "PRINCIPLES.md"))})`,
`- Guidance: \`.sf/TASTE.md\` (${present(join(sfDir, "TASTE.md"))})`, `- Guidance: \`.sf/STYLE.md\` (${present(join(sfDir, "STYLE.md"))})`,
`- Guidance: \`.sf/ANTI-GOALS.md\` (${present(join(sfDir, "ANTI-GOALS.md"))})`, `- Guidance: \`.sf/NON-GOALS.md\` (${present(join(sfDir, "NON-GOALS.md"))})`,
`- Optional knowledge: \`.sf/KNOWLEDGE.md\` (${present(join(sfDir, "KNOWLEDGE.md"))})`, `- Optional knowledge: \`.sf/KNOWLEDGE.md\` (${present(join(sfDir, "KNOWLEDGE.md"))})`,
`- Optional preferences: \`.sf/preferences.yaml\` (${present(join(sfDir, "preferences.yaml"))})`, `- Optional preferences: \`.sf/preferences.yaml\` (${present(join(sfDir, "preferences.yaml"))})`,
readDbSummary(basePath), readDbSummary(basePath),
@ -193,7 +193,7 @@ export function generateOperatingModelSpec(basePath) {
"", "",
"Markdown under `.sf/` has two roles:", "Markdown under `.sf/` has two roles:",
"", "",
"- working guidance and knowledge that the runtime loads, such as `PRINCIPLES.md`, `TASTE.md`, `ANTI-GOALS.md`, `KNOWLEDGE.md`, and `preferences.yaml`;", "- 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.", "- 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.", "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.",
@ -234,7 +234,7 @@ export function generateOperatingModelSpec(basePath) {
"", "",
"- `.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/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/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/TASTE.md`, `.sf/ANTI-GOALS.md`, `.sf/KNOWLEDGE.md`, and `.sf/preferences.yaml` are repo-local working guidance files when present.", "- `.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.", "- 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.", "- 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.",
"", "",