fix: exclude generated sf milestones from staging
This commit is contained in:
parent
4f39c3f4c8
commit
88cf545821
3 changed files with 24 additions and 28 deletions
|
|
@ -5,10 +5,11 @@
|
|||
* cleanup, .gitignore bootstrapping, and diagnostics must keep out of commits.
|
||||
*/
|
||||
/**
|
||||
* Lists SF runtime paths that should stay out of user commits.
|
||||
* Lists SF runtime/generated paths that should stay out of user commits.
|
||||
*
|
||||
* Purpose: keep generated state, locks, databases, and continuation files from
|
||||
* polluting project history while allowing durable planning artifacts to remain trackable.
|
||||
* Purpose: keep generated state, locks, databases, milestone workspaces, and
|
||||
* continuation files from polluting project history. Durable plans/specs/ADRs
|
||||
* are promoted to docs instead of committed from `.sf/milestones`.
|
||||
*
|
||||
* Consumer: gitignore.ts for .git/info/exclude bootstrapping and git-service.ts for staging exclusions.
|
||||
*/
|
||||
|
|
@ -22,6 +23,7 @@ export const SF_RUNTIME_PATTERNS = [
|
|||
".sf/parallel/",
|
||||
".sf/reports/",
|
||||
".sf/runtime/",
|
||||
".sf/milestones/",
|
||||
".sf/worktrees/",
|
||||
".sf/auto.lock",
|
||||
".sf/metrics.json",
|
||||
|
|
@ -38,6 +40,4 @@ export const SF_RUNTIME_PATTERNS = [
|
|||
".sf/SELF-FEEDBACK.md",
|
||||
".sf/repo-meta.json",
|
||||
".sf/DISCUSSION-MANIFEST.json",
|
||||
".sf/milestones/**/*-CONTINUE.md",
|
||||
".sf/milestones/**/continue.md",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -405,12 +405,10 @@ export class GitServiceImpl {
|
|||
// in a dedicated commit. This must happen as a separate commit because
|
||||
// the git reset HEAD step below would otherwise undo the rm --cached.
|
||||
//
|
||||
// SAFETY: Only untrack the specific RUNTIME paths (activity/, runtime/,
|
||||
// auto.lock, etc.) — NOT all of .sf/. If .sf/milestones/ files were
|
||||
// previously tracked, they stay tracked until the milestone completes
|
||||
// and the worktree is torn down. This prevents a mid-execution behavioral
|
||||
// discontinuity where the first half of a milestone has .sf/ artifacts
|
||||
// committed but the second half doesn't (#1326).
|
||||
// SAFETY: Only untrack known runtime/generated paths (activity/, runtime/,
|
||||
// milestones/, auto.lock, etc.) — NOT all of .sf/. Human-authored .sf
|
||||
// guidance can remain tracked, while generated milestone workspaces are
|
||||
// promoted to docs before becoming durable project artifacts.
|
||||
if (!this._runtimeFilesCleanedUp) {
|
||||
let cleaned = false;
|
||||
for (const exclusion of RUNTIME_EXCLUSION_PATHS) {
|
||||
|
|
@ -430,12 +428,9 @@ export class GitServiceImpl {
|
|||
// hashed by git. The old approach of `git add -A` followed by unstaging
|
||||
// hangs indefinitely on repos with large untracked artifact trees (#1605).
|
||||
//
|
||||
// Exclude only RUNTIME paths from staging — not the entire .sf/ directory.
|
||||
// When .sf/milestones/ files are already tracked in the index (projects
|
||||
// where .sf/ is not gitignored, or Windows junctions that git sees as
|
||||
// real directories), they should continue to be committed. Excluding the
|
||||
// entire .sf/ directory mid-milestone causes silent commit failure where
|
||||
// the second half of a milestone's artifacts are never committed (#1326).
|
||||
// Exclude runtime/generated paths from staging — not the entire .sf/
|
||||
// directory. This keeps deliberate .sf guidance trackable while preventing
|
||||
// generated milestone workspaces from becoming peer source-of-truth files.
|
||||
//
|
||||
// If .sf/ IS in .gitignore (the default for external state projects),
|
||||
// git add -A already skips it and the exclusions are harmless no-ops.
|
||||
|
|
|
|||
|
|
@ -19,14 +19,15 @@ import { bodyHash as preferencesBodyHash } from "./scaffold-versioning.js";
|
|||
export { SF_RUNTIME_PATTERNS } from "./git-runtime-patterns.js";
|
||||
|
||||
/**
|
||||
* SF runtime exclusion patterns for repos where .sf/ is a LOCAL DIRECTORY.
|
||||
* Granular so that durable planning artifacts (.sf/milestones/, .sf/PROJECT.md,
|
||||
* .sf/DECISIONS.md) remain trackable in git per ADR-001.
|
||||
* SF runtime/generated exclusion patterns for repos where .sf/ is a LOCAL DIRECTORY.
|
||||
* Granular so deliberate human-authored guidance such as .sf/PRINCIPLES.md,
|
||||
* .sf/TASTE.md, and .sf/ANTI-GOALS.md can remain trackable.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Migrated from blanket `.sf` on 2026-05-01 to implement ADR-001.
|
||||
* Migrated from blanket `.sf` on 2026-05-01; later tightened so
|
||||
* .sf/milestones is generated runtime state unless promoted to docs.
|
||||
* Previously migrated out of BASELINE_PATTERNS into .git/info/exclude on 2026-04-29.
|
||||
*/
|
||||
const SF_RUNTIME_EXCLUSION_PATTERNS = [
|
||||
|
|
@ -160,7 +161,7 @@ export function hasGitTrackedSfFiles(basePath) {
|
|||
* all other baseline patterns are still applied normally.
|
||||
*/
|
||||
/**
|
||||
* Write sf-specific runtime exclusion patterns (`.sf`, `.sf-id`, `.bg-shell/`)
|
||||
* Write sf-specific runtime/generated exclusion patterns (`.sf`, `.sf-id`, `.bg-shell/`)
|
||||
* to `.git/info/exclude` — per-clone, never committed, never causes
|
||||
* working-tree churn. Idempotent: only writes when something is missing.
|
||||
*
|
||||
|
|
@ -183,8 +184,8 @@ export function ensureGitInfoExclude(basePath) {
|
|||
: "";
|
||||
// Determine whether .sf is a symlink (external state) or a local directory.
|
||||
// Symlink: git cannot traverse it, so only the blanket .sf pattern works.
|
||||
// Directory: use granular patterns so .sf/milestones/ and other durable
|
||||
// planning artifacts can be tracked per ADR-001.
|
||||
// Directory: use granular patterns so deliberate human-authored .sf guidance
|
||||
// can be tracked while generated runtime state stays ignored.
|
||||
const sfIsSymlink = (() => {
|
||||
const localSf = join(basePath, ".sf");
|
||||
try {
|
||||
|
|
@ -215,7 +216,7 @@ export function ensureGitInfoExclude(basePath) {
|
|||
if (missing.length > 0) {
|
||||
const block = [
|
||||
"",
|
||||
"# ── SF runtime exclusion (managed by sf, per-clone) ──",
|
||||
"# ── SF runtime/generated exclusion (managed by sf, per-clone) ──",
|
||||
...missing,
|
||||
"",
|
||||
].join("\n");
|
||||
|
|
@ -272,9 +273,9 @@ export function ensureGitignore(basePath, options) {
|
|||
*
|
||||
* Only removes from the index (`--cached`), never from disk. Idempotent.
|
||||
*
|
||||
* Note: These are strictly runtime/ephemeral paths (activity logs, lock files,
|
||||
* metrics, STATE.md). They are always safe to untrack, even when the project
|
||||
* intentionally keeps other `.sf/` files (like PROJECT.md, milestones/) in
|
||||
* Note: These are strictly runtime/generated paths (activity logs, lock files,
|
||||
* metrics, STATE.md, milestone workspaces). They are always safe to untrack,
|
||||
* even when the project intentionally keeps human-authored `.sf/` guidance in
|
||||
* version control.
|
||||
*/
|
||||
export function untrackRuntimeFiles(basePath) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue