All other .gsd/ state files use uppercase naming (DECISIONS.md, REQUIREMENTS.md, PROJECT.md, etc). This renames the canonical preferences file to PREFERENCES.md while keeping a migration fallback — the loader checks PREFERENCES.md first, then falls back to lowercase preferences.md for existing installations. Closes #2700 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6172246772
commit
a952391b33
39 changed files with 90 additions and 90 deletions
|
|
@ -11,7 +11,7 @@ Users on capped plans (e.g., Claude Pro) exhaust weekly token limits in 15-20 ho
|
|||
## Current Architecture
|
||||
|
||||
### What Exists
|
||||
- **Phase-based model config:** Users can set different models per phase via `preferences.md` (research, planning, execution, completion)
|
||||
- **Phase-based model config:** Users can set different models per phase via `PREFERENCES.md` (research, planning, execution, completion)
|
||||
- **Fallback chains:** Each phase supports `fallbacks: [model1, model2]` for error recovery
|
||||
- **Pre-dispatch hooks:** `PreDispatchResult` has a `model` field but it's **never applied** in `auto.ts` — this is a ready-made extension point
|
||||
- **Model registry:** `ModelRegistry.getAvailable()` provides all configured models with metadata
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ Quick filesystem scan (no heavy reads):
|
|||
|
||||
### Task 1.4: `isFirstEverLaunch(): boolean`
|
||||
|
||||
Returns `true` if `~/.gsd/` doesn't exist or has no `preferences.md`.
|
||||
Returns `true` if `~/.gsd/` doesn't exist or has no `PREFERENCES.md`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ Step 8: Advanced (collapsed by default, expandable)
|
|||
|
||||
Step 9: Bootstrap .gsd/ structure
|
||||
- Creates .gsd/milestones/
|
||||
- Creates .gsd/preferences.md (from wizard answers)
|
||||
- Creates .gsd/PREFERENCES.md (from wizard answers)
|
||||
- Creates .gitignore entries
|
||||
- Seeds CONTEXT.md with detected project signals
|
||||
- Commits "chore: init gsd" (if commit_docs enabled)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ The `/gsd prefs wizard` currently only configures 6 of 18+ preference fields. Us
|
|||
- Added missing keys to `orderedKeys` in `serializePreferencesToFrontmatter()`
|
||||
|
||||
### Group 6: Update Template & Docs ✓
|
||||
- Updated `templates/preferences.md` with new fields
|
||||
- Updated `templates/PREFERENCES.md` with new fields
|
||||
- Updated `docs/preferences-reference.md` with budget, notifications, git, hooks
|
||||
|
||||
### Group 7: Tests ✓
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ git rebase origin/main
|
|||
GSD uses worktree-based isolation for multi-developer work. If you're contributing with GSD running, enable team mode in your project preferences:
|
||||
|
||||
```yaml
|
||||
# .gsd/preferences.md
|
||||
# .gsd/PREFERENCES.md
|
||||
---
|
||||
version: 1
|
||||
mode: team
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ An auto-generated `index.html` shows all reports with progression metrics across
|
|||
|
||||
### Preferences
|
||||
|
||||
GSD preferences live in `~/.gsd/preferences.md` (global) or `.gsd/preferences.md` (project). Manage with `/gsd prefs`.
|
||||
GSD preferences live in `~/.gsd/PREFERENCES.md` (global) or `.gsd/PREFERENCES.md` (project). Manage with `/gsd prefs`.
|
||||
|
||||
```yaml
|
||||
---
|
||||
|
|
@ -672,7 +672,7 @@ The best practice for working in teams is to ensure unique milestone names acros
|
|||
|
||||
### Unique Milestone Names
|
||||
|
||||
Create or amend your `.gsd/preferences.md` file within the repo to include `unique_milestone_ids: true` e.g.
|
||||
Create or amend your `.gsd/PREFERENCES.md` file within the repo to include `unique_milestone_ids: true` e.g.
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
|
@ -681,7 +681,7 @@ unique_milestone_ids: true
|
|||
---
|
||||
```
|
||||
|
||||
With the above `.gitignore` set up, the `.gsd/preferences.md` file is checked into the repo ensuring all teammates use unique milestone names to avoid collisions.
|
||||
With the above `.gitignore` set up, the `.gsd/PREFERENCES.md` file is checked into the repo ensuring all teammates use unique milestone names to avoid collisions.
|
||||
|
||||
Milestone names will now be generated with a 6 char random string appended e.g. instead of `M001` you'll get something like `M001-ush8s3`
|
||||
|
||||
|
|
@ -689,7 +689,7 @@ Milestone names will now be generated with a 6 char random string appended e.g.
|
|||
|
||||
1. Ensure you are not in the middle of any milestones (clean state)
|
||||
2. Update the `.gsd/` related entries in your `.gitignore` to follow the `Suggested .gitignore setup` section under `Working in teams` (ensure you are no longer blanket ignoring the whole `.gsd/` directory)
|
||||
3. Update your `.gsd/preferences.md` file within the repo as per section `Unique Milestone Names`
|
||||
3. Update your `.gsd/PREFERENCES.md` file within the repo as per section `Unique Milestone Names`
|
||||
4. If you want to update all your existing milestones use this prompt in GSD: `I have turned on unique milestone ids, please update all old milestone ids to use this new format e.g. M001-abc123 where abc123 is a random 6 char lowercase alpha numeric string. Update all references in all .gsd file contents, file names and directory names. Validate your work once done to ensure referential integrity.`
|
||||
5. Commit to git
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
# Configuration
|
||||
|
||||
GSD preferences live in `~/.gsd/preferences.md` (global) or `.gsd/preferences.md` (project-local). Manage interactively with `/gsd prefs`.
|
||||
GSD preferences live in `~/.gsd/PREFERENCES.md` (global) or `.gsd/PREFERENCES.md` (project-local). Manage interactively with `/gsd prefs`.
|
||||
|
||||
## `/gsd prefs` Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/gsd prefs` | Open the global preferences wizard (default) |
|
||||
| `/gsd prefs global` | Interactive wizard for global preferences (`~/.gsd/preferences.md`) |
|
||||
| `/gsd prefs project` | Interactive wizard for project preferences (`.gsd/preferences.md`) |
|
||||
| `/gsd prefs global` | Interactive wizard for global preferences (`~/.gsd/PREFERENCES.md`) |
|
||||
| `/gsd prefs project` | Interactive wizard for project preferences (`.gsd/PREFERENCES.md`) |
|
||||
| `/gsd prefs status` | Show current preference files, merged values, and skill resolution status |
|
||||
| `/gsd prefs wizard` | Alias for `/gsd prefs global` |
|
||||
| `/gsd prefs setup` | Alias for `/gsd prefs wizard` — creates preferences file if missing |
|
||||
|
|
@ -42,8 +42,8 @@ token_profile: balanced
|
|||
|
||||
| Scope | Path | Applies to |
|
||||
|-------|------|-----------|
|
||||
| Global | `~/.gsd/preferences.md` | All projects |
|
||||
| Project | `.gsd/preferences.md` | Current project only |
|
||||
| Global | `~/.gsd/PREFERENCES.md` | All projects |
|
||||
| Project | `.gsd/PREFERENCES.md` | Current project only |
|
||||
|
||||
**Merge behavior:**
|
||||
- **Scalar fields** (`skill_discovery`, `budget_ceiling`): project wins if defined
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ File overlaps are warnings, not blockers. Both milestones work in separate workt
|
|||
|
||||
## Configuration
|
||||
|
||||
Add to `~/.gsd/preferences.md` or `.gsd/preferences.md`:
|
||||
Add to `~/.gsd/PREFERENCES.md` or `.gsd/PREFERENCES.md`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ The setup wizard:
|
|||
3. Lists servers the bot belongs to (or lets you pick)
|
||||
4. Lists text channels in the selected server
|
||||
5. Sends a test message to confirm permissions
|
||||
6. Saves the configuration to `~/.gsd/preferences.md`
|
||||
6. Saves the configuration to `~/.gsd/PREFERENCES.md`
|
||||
|
||||
**Bot requirements:**
|
||||
- A Discord bot application with a token (from [Discord Developer Portal](https://discord.com/developers/applications))
|
||||
|
|
@ -65,7 +65,7 @@ The setup wizard:
|
|||
|
||||
## Configuration
|
||||
|
||||
Remote questions are configured in `~/.gsd/preferences.md`:
|
||||
Remote questions are configured in `~/.gsd/PREFERENCES.md`:
|
||||
|
||||
```yaml
|
||||
remote_questions:
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ models:
|
|||
## How the Pieces Fit Together
|
||||
|
||||
```
|
||||
preferences.md
|
||||
PREFERENCES.md
|
||||
└─ token_profile: balanced
|
||||
├─ resolveProfileDefaults() → model defaults + phase skip defaults
|
||||
├─ resolveInlineLevel() → standard
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ GSD supports multi-user workflows where several developers work on the same repo
|
|||
The simplest way to configure GSD for team use is to set `mode: team` in your project preferences. This enables unique milestone IDs, push branches, and pre-merge checks in one setting:
|
||||
|
||||
```yaml
|
||||
# .gsd/preferences.md (project-level, committed to git)
|
||||
# .gsd/PREFERENCES.md (project-level, committed to git)
|
||||
---
|
||||
version: 1
|
||||
mode: team
|
||||
|
|
@ -38,7 +38,7 @@ Share planning artifacts (milestones, roadmaps, decisions) while keeping runtime
|
|||
```
|
||||
|
||||
**What gets shared** (committed to git):
|
||||
- `.gsd/preferences.md` — project preferences
|
||||
- `.gsd/PREFERENCES.md` — project preferences
|
||||
- `.gsd/PROJECT.md` — living project description
|
||||
- `.gsd/REQUIREMENTS.md` — requirement contract
|
||||
- `.gsd/DECISIONS.md` — architectural decisions
|
||||
|
|
@ -50,7 +50,7 @@ Share planning artifacts (milestones, roadmaps, decisions) while keeping runtime
|
|||
### 3. Commit the Preferences
|
||||
|
||||
```bash
|
||||
git add .gsd/preferences.md
|
||||
git add .gsd/PREFERENCES.md
|
||||
git commit -m "chore: enable GSD team workflow"
|
||||
```
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ If you have an existing project with `.gsd/` blanket-ignored:
|
|||
|
||||
1. Ensure no milestones are in progress (clean state)
|
||||
2. Update `.gitignore` to use the selective pattern above
|
||||
3. Add `unique_milestone_ids: true` to `.gsd/preferences.md`
|
||||
3. Add `unique_milestone_ids: true` to `.gsd/PREFERENCES.md`
|
||||
4. Optionally rename existing milestones to use unique IDs:
|
||||
```
|
||||
I have turned on unique milestone ids, please update all old milestone
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ title: "Configuration"
|
|||
description: "Preferences, model selection, MCP servers, hooks, and all settings."
|
||||
---
|
||||
|
||||
GSD preferences live in `~/.gsd/preferences.md` (global) or `.gsd/preferences.md` (project-local). Manage interactively with `/gsd prefs`.
|
||||
GSD preferences live in `~/.gsd/PREFERENCES.md` (global) or `.gsd/PREFERENCES.md` (project-local). Manage interactively with `/gsd prefs`.
|
||||
|
||||
## Preferences commands
|
||||
|
||||
|
|
@ -40,8 +40,8 @@ token_profile: balanced
|
|||
|
||||
| Scope | Path | Applies to |
|
||||
|-------|------|-----------|
|
||||
| Global | `~/.gsd/preferences.md` | All projects |
|
||||
| Project | `.gsd/preferences.md` | Current project only |
|
||||
| Global | `~/.gsd/PREFERENCES.md` | All projects |
|
||||
| Project | `.gsd/PREFERENCES.md` | Current project only |
|
||||
|
||||
**Merge behavior:**
|
||||
- **Scalar fields** — project wins if defined
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ GSD supports multi-user workflows where several developers work on the same repo
|
|||
### 1. Set team mode
|
||||
|
||||
```yaml
|
||||
# .gsd/preferences.md (project-level, committed to git)
|
||||
# .gsd/PREFERENCES.md (project-level, committed to git)
|
||||
---
|
||||
version: 1
|
||||
mode: team
|
||||
|
|
@ -43,7 +43,7 @@ Share planning artifacts while keeping runtime files local:
|
|||
### 3. Commit
|
||||
|
||||
```bash
|
||||
git add .gsd/preferences.md
|
||||
git add .gsd/PREFERENCES.md
|
||||
git commit -m "chore: enable GSD team workflow"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { appRoot } from "./app-paths.js";
|
|||
// boundary — this file is compiled by tsc, but preferences.ts is loaded
|
||||
// via jiti at runtime. Importing it as .js fails because no .js exists
|
||||
// in dist/. See #592, #1110.
|
||||
const GLOBAL_PREFERENCES_PATH = join(appRoot, "preferences.md");
|
||||
const GLOBAL_PREFERENCES_PATH = join(appRoot, "PREFERENCES.md");
|
||||
|
||||
export function saveRemoteQuestionsConfig(channel: "slack" | "discord" | "telegram", channelId: string): void {
|
||||
const prefsPath = GLOBAL_PREFERENCES_PATH;
|
||||
|
|
|
|||
|
|
@ -771,7 +771,7 @@ export async function ensurePreferencesFile(
|
|||
scope: "global" | "project",
|
||||
): Promise<void> {
|
||||
if (!existsSync(path)) {
|
||||
const template = await loadFile(join(dirname(fileURLToPath(import.meta.url)), "templates", "preferences.md"));
|
||||
const template = await loadFile(join(dirname(fileURLToPath(import.meta.url)), "templates", "PREFERENCES.md"));
|
||||
if (!template) {
|
||||
ctx.ui.notify("Could not load GSD preferences template.", "error");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -359,8 +359,8 @@ function detectV2Gsd(basePath: string): V2Detection | null {
|
|||
if (!existsSync(gsdPath)) return null;
|
||||
|
||||
const hasPreferences =
|
||||
existsSync(join(gsdPath, "preferences.md")) ||
|
||||
existsSync(join(gsdPath, "PREFERENCES.md"));
|
||||
existsSync(join(gsdPath, "PREFERENCES.md")) ||
|
||||
existsSync(join(gsdPath, "preferences.md"));
|
||||
|
||||
const hasContext = existsSync(join(gsdPath, "CONTEXT.md"));
|
||||
|
||||
|
|
@ -714,8 +714,8 @@ function detectVerificationCommands(
|
|||
*/
|
||||
export function hasGlobalSetup(): boolean {
|
||||
return (
|
||||
existsSync(join(gsdHome, "preferences.md")) ||
|
||||
existsSync(join(gsdHome, "PREFERENCES.md"))
|
||||
existsSync(join(gsdHome, "PREFERENCES.md")) ||
|
||||
existsSync(join(gsdHome, "preferences.md"))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -728,8 +728,8 @@ export function isFirstEverLaunch(): boolean {
|
|||
|
||||
// If we have preferences, not first launch
|
||||
if (
|
||||
existsSync(join(gsdHome, "preferences.md")) ||
|
||||
existsSync(join(gsdHome, "PREFERENCES.md"))
|
||||
existsSync(join(gsdHome, "PREFERENCES.md")) ||
|
||||
existsSync(join(gsdHome, "preferences.md"))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# GSD Preferences Reference
|
||||
|
||||
Full documentation for `~/.gsd/preferences.md` (global) and `.gsd/preferences.md` (project).
|
||||
Full documentation for `~/.gsd/PREFERENCES.md` (global) and `.gsd/PREFERENCES.md` (project).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -51,8 +51,8 @@ skill_rules: []
|
|||
|
||||
Preferences are loaded from two locations and merged:
|
||||
|
||||
1. **Global:** `~/.gsd/preferences.md` — applies to all projects
|
||||
2. **Project:** `.gsd/preferences.md` — applies to the current project only
|
||||
1. **Global:** `~/.gsd/PREFERENCES.md` — applies to all projects
|
||||
2. **Project:** `.gsd/PREFERENCES.md` — applies to the current project only
|
||||
|
||||
**Merge behavior** (see `mergePreferences()` in `preferences.ts`):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* GSD bootstrappers for .gitignore and preferences.md
|
||||
* GSD bootstrappers for .gitignore and PREFERENCES.md
|
||||
*
|
||||
* Ensures baseline .gitignore exists with universally-correct patterns.
|
||||
* Creates an empty preferences.md template if it doesn't exist.
|
||||
* Creates an empty PREFERENCES.md template if it doesn't exist.
|
||||
* Both idempotent — non-destructive if already present.
|
||||
*/
|
||||
|
||||
|
|
@ -216,16 +216,16 @@ export function untrackRuntimeFiles(basePath: string): void {
|
|||
}
|
||||
|
||||
/**
|
||||
* Ensure basePath/.gsd/preferences.md exists as an empty template.
|
||||
* Ensure basePath/.gsd/PREFERENCES.md exists as an empty template.
|
||||
* Creates the file with frontmatter only if it doesn't exist.
|
||||
* Returns true if created, false if already exists.
|
||||
*
|
||||
* Checks both lowercase (canonical) and uppercase (legacy) to avoid
|
||||
* creating a duplicate when an uppercase file already exists.
|
||||
* Checks both uppercase (canonical) and lowercase (legacy) to avoid
|
||||
* creating a duplicate when a lowercase file already exists.
|
||||
*/
|
||||
export function ensurePreferences(basePath: string): boolean {
|
||||
const preferencesPath = join(gsdRoot(basePath), "preferences.md");
|
||||
const legacyPath = join(gsdRoot(basePath), "PREFERENCES.md");
|
||||
const preferencesPath = join(gsdRoot(basePath), "PREFERENCES.md");
|
||||
const legacyPath = join(gsdRoot(basePath), "preferences.md");
|
||||
|
||||
if (existsSync(preferencesPath) || existsSync(legacyPath)) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -422,9 +422,9 @@ function bootstrapGsdDirectory(
|
|||
const gsd = gsdRoot(basePath);
|
||||
mkdirSync(join(gsd, "milestones"), { recursive: true });
|
||||
|
||||
// Write preferences.md from wizard answers
|
||||
// Write PREFERENCES.md from wizard answers
|
||||
const preferencesContent = buildPreferencesFile(prefs);
|
||||
writeFileSync(join(gsd, "preferences.md"), preferencesContent, "utf-8");
|
||||
writeFileSync(join(gsd, "PREFERENCES.md"), preferencesContent, "utf-8");
|
||||
|
||||
// Seed CONTEXT.md with detected project signals
|
||||
const contextContent = buildContextSeed(signals);
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ export function resolveContextSelection(): import("./types.js").ContextSelection
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolve the search provider preference from preferences.md.
|
||||
* Resolve the search provider preference from PREFERENCES.md.
|
||||
* Returns undefined if not configured (caller falls back to existing behavior).
|
||||
*/
|
||||
export function resolveSearchProviderFromPreferences(): GSDPreferences["search_provider"] | undefined {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ function gsdHome(): string {
|
|||
}
|
||||
|
||||
function globalPreferencesPath(): string {
|
||||
return join(gsdHome(), "preferences.md");
|
||||
return join(gsdHome(), "PREFERENCES.md");
|
||||
}
|
||||
|
||||
function legacyGlobalPreferencesPath(): string {
|
||||
|
|
@ -95,16 +95,16 @@ function legacyGlobalPreferencesPath(): string {
|
|||
}
|
||||
|
||||
function projectPreferencesPath(): string {
|
||||
return join(gsdRoot(process.cwd()), "preferences.md");
|
||||
}
|
||||
// Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
|
||||
// Check uppercase as a fallback so those files aren't silently ignored.
|
||||
function globalPreferencesPathUppercase(): string {
|
||||
return join(gsdHome(), "PREFERENCES.md");
|
||||
}
|
||||
function projectPreferencesPathUppercase(): string {
|
||||
return join(gsdRoot(process.cwd()), "PREFERENCES.md");
|
||||
}
|
||||
// Legacy: older versions used lowercase preferences.md.
|
||||
// Check lowercase as a fallback so those files aren't silently ignored.
|
||||
function globalPreferencesPathLegacy(): string {
|
||||
return join(gsdHome(), "preferences.md");
|
||||
}
|
||||
function projectPreferencesPathLegacy(): string {
|
||||
return join(gsdRoot(process.cwd()), "preferences.md");
|
||||
}
|
||||
|
||||
export function getGlobalGSDPreferencesPath(): string {
|
||||
return globalPreferencesPath();
|
||||
|
|
@ -122,13 +122,13 @@ export function getProjectGSDPreferencesPath(): string {
|
|||
|
||||
export function loadGlobalGSDPreferences(): LoadedGSDPreferences | null {
|
||||
return loadPreferencesFile(globalPreferencesPath(), "global")
|
||||
?? loadPreferencesFile(globalPreferencesPathUppercase(), "global")
|
||||
?? loadPreferencesFile(globalPreferencesPathLegacy(), "global")
|
||||
?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
|
||||
}
|
||||
|
||||
export function loadProjectGSDPreferences(): LoadedGSDPreferences | null {
|
||||
return loadPreferencesFile(projectPreferencesPath(), "project")
|
||||
?? loadPreferencesFile(projectPreferencesPathUppercase(), "project");
|
||||
?? loadPreferencesFile(projectPreferencesPathLegacy(), "project");
|
||||
}
|
||||
|
||||
export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
|
||||
|
|
@ -223,7 +223,7 @@ export function parsePreferencesMarkdown(content: string): GSDPreferences | null
|
|||
|
||||
if (!_warnedUnrecognizedFormat) {
|
||||
_warnedUnrecognizedFormat = true;
|
||||
console.warn("[parsePreferencesMarkdown] preferences.md exists but uses an unrecognized format — skipping.");
|
||||
console.warn("[parsePreferencesMarkdown] PREFERENCES.md exists but uses an unrecognized format — skipping.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
@ -502,7 +502,7 @@ export function resolvePreDispatchHooks(): PreDispatchHookConfig[] {
|
|||
* Resolve the effective git isolation mode from preferences.
|
||||
* Returns "none" (default), "worktree", or "branch".
|
||||
*
|
||||
* Default is "none" so GSD works out of the box without preferences.md.
|
||||
* Default is "none" so GSD works out of the box without PREFERENCES.md.
|
||||
* Worktree isolation requires explicit opt-in because it depends on git
|
||||
* branch infrastructure that must be set up before use.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ Titles live inside file content (headings, frontmatter), not in file or director
|
|||
|
||||
### Isolation Model
|
||||
|
||||
Auto-mode supports three isolation modes (configured in `.gsd/preferences.md` under `taskIsolation.mode`):
|
||||
Auto-mode supports three isolation modes (configured in `.gsd/PREFERENCES.md` under `taskIsolation.mode`):
|
||||
|
||||
- **worktree** (default): Work happens in `.gsd/worktrees/<MID>/`, a full git worktree on the `milestone/<MID>` branch. Each worktree has its own working copy and `.gsd/` directory. Squash-merged back to the integration branch on milestone completion.
|
||||
- **branch**: Work happens in the project root on a `milestone/<MID>` branch. No worktree directory — files are checked out in-place.
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@ export class RuleRegistry {
|
|||
formatHookStatus(): string {
|
||||
const entries = this.getHookStatus();
|
||||
if (entries.length === 0) {
|
||||
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/preferences.md";
|
||||
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/PREFERENCES.md";
|
||||
}
|
||||
|
||||
const lines: string[] = ["Configured Hooks:", ""];
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ describe(
|
|||
|
||||
before(() => {
|
||||
tempDir = mkdtempSync(join(tmpdir(), 'gsd-tui-test-'));
|
||||
prefsPath = join(tempDir, 'preferences.md');
|
||||
prefsPath = join(tempDir, 'PREFERENCES.md');
|
||||
prefs = { version: 1 };
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ test("detectProjectState: detects preferences in .gsd/", (t) => {
|
|||
t.after(() => cleanup(dir));
|
||||
|
||||
mkdirSync(join(dir, ".gsd", "milestones"), { recursive: true });
|
||||
writeFileSync(join(dir, ".gsd", "preferences.md"), "---\nversion: 1\n---\n", "utf-8");
|
||||
writeFileSync(join(dir, ".gsd", "PREFERENCES.md"), "---\nversion: 1\n---\n", "utf-8");
|
||||
const result = detectProjectState(dir);
|
||||
assert.ok(result.v2);
|
||||
assert.equal(result.v2!.hasPreferences, true);
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ _None_
|
|||
return dir;
|
||||
}
|
||||
|
||||
/** Write a .gsd/preferences.md with the given git isolation mode. */
|
||||
/** Write a .gsd/PREFERENCES.md with the given git isolation mode. */
|
||||
function writePreferencesFile(dir: string, isolation: "none" | "worktree" | "branch"): void {
|
||||
const gsdDir = join(dir, ".gsd");
|
||||
mkdirSync(gsdDir, { recursive: true });
|
||||
writeFileSync(join(gsdDir, "preferences.md"), `---\ngit:\n isolation: "${isolation}"\n---\n`);
|
||||
writeFileSync(join(gsdDir, "PREFERENCES.md"), `---\ngit:\n isolation: "${isolation}"\n---\n`);
|
||||
}
|
||||
|
||||
/** Create a repo with an in-progress milestone. */
|
||||
|
|
@ -302,7 +302,7 @@ describe('doctor-git', async () => {
|
|||
// ─── Test 7: none-mode skips orphaned worktree check ───────────────
|
||||
// NOTE: loadEffectiveGSDPreferences() resolves PROJECT_PREFERENCES_PATH
|
||||
// at module load time from process.cwd(). We write the prefs file to
|
||||
// the test runner's cwd .gsd/preferences.md and clean up afterwards.
|
||||
// the test runner's cwd .gsd/PREFERENCES.md and clean up afterwards.
|
||||
if (process.platform !== "win32") {
|
||||
test('none-mode skips orphaned worktree', async () => {
|
||||
const dir = createRepoWithCompletedMilestone();
|
||||
|
|
@ -409,7 +409,7 @@ describe('doctor-git', async () => {
|
|||
cleanups.push(dir);
|
||||
|
||||
run("git branch trunk", dir);
|
||||
writeFileSync(join(dir, ".gsd", "preferences.md"), `---\ngit:\n isolation: "worktree"\n main_branch: "trunk"\n---\n`);
|
||||
writeFileSync(join(dir, ".gsd", "PREFERENCES.md"), `---\ngit:\n isolation: "worktree"\n main_branch: "trunk"\n---\n`);
|
||||
|
||||
const metaPath = join(dir, ".gsd", "milestones", "M001", "M001-META.json");
|
||||
writeFileSync(metaPath, JSON.stringify({ integrationBranch: "feat/does-not-exist" }, null, 2));
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ describe('doctor-proactive', async () => {
|
|||
cleanups.push(dir);
|
||||
|
||||
run("git branch trunk", dir);
|
||||
writeFileSync(join(dir, ".gsd", "preferences.md"), `---\ngit:\n main_branch: "trunk"\n---\n`);
|
||||
writeFileSync(join(dir, ".gsd", "PREFERENCES.md"), `---\ngit:\n main_branch: "trunk"\n---\n`);
|
||||
const metaPath = join(dir, ".gsd", "milestones", "M001", "M001-META.json");
|
||||
writeFileSync(metaPath, JSON.stringify({ integrationBranch: "feature/missing" }, null, 2));
|
||||
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ test("runProviderChecks uses provider-qualified anthropic-vertex model IDs", ()
|
|||
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-prefix-repo-")));
|
||||
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(repo, ".gsd", "preferences.md"),
|
||||
join(repo, ".gsd", "PREFERENCES.md"),
|
||||
[
|
||||
"---",
|
||||
"models:",
|
||||
|
|
@ -454,7 +454,7 @@ test("runProviderChecks uses object provider field for anthropic-vertex models",
|
|||
const repo = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-vertex-provider-repo-")));
|
||||
mkdirSync(join(repo, ".gsd"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(repo, ".gsd", "preferences.md"),
|
||||
join(repo, ".gsd", "PREFERENCES.md"),
|
||||
[
|
||||
"---",
|
||||
"models:",
|
||||
|
|
|
|||
|
|
@ -1142,7 +1142,7 @@ describe('git-service', async () => {
|
|||
mkdirSync(join(repo, ".gsd", "runtime"), { recursive: true });
|
||||
mkdirSync(join(repo, ".gsd", "activity"), { recursive: true });
|
||||
writeFileSync(join(repo, ".gsd", "milestones", "M001", "ROADMAP.md"), "# Roadmap");
|
||||
writeFileSync(join(repo, ".gsd", "preferences.md"), "---\nversion: 1\n---");
|
||||
writeFileSync(join(repo, ".gsd", "PREFERENCES.md"), "---\nversion: 1\n---");
|
||||
writeFileSync(join(repo, ".gsd", "STATE.md"), "# State");
|
||||
writeFileSync(join(repo, ".gsd", "runtime", "units.json"), "{}");
|
||||
writeFileSync(join(repo, ".gsd", "activity", "log.jsonl"), "{}");
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ test("init-wizard: v2 .gsd/ preferences detected", (t) => {
|
|||
const dir = makeTempDir("prefs-detect");
|
||||
try {
|
||||
mkdirSync(join(dir, ".gsd", "milestones"), { recursive: true });
|
||||
writeFileSync(join(dir, ".gsd", "preferences.md"), "---\nversion: 1\nmode: solo\n---\n", "utf-8");
|
||||
writeFileSync(join(dir, ".gsd", "PREFERENCES.md"), "---\nversion: 1\nmode: solo\n---\n", "utf-8");
|
||||
|
||||
const detection = detectProjectState(dir);
|
||||
assert.ok(detection.v2);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* Uses the writeRunnerPreferences pattern from doctor-git.test.ts:
|
||||
* PROJECT_PREFERENCES_PATH is a module-level constant frozen at import
|
||||
* time, so process.chdir() won't redirect preference loading. We write
|
||||
* prefs to the runner's cwd .gsd/preferences.md and clean up in finally.
|
||||
* prefs to the runner's cwd .gsd/PREFERENCES.md and clean up in finally.
|
||||
*/
|
||||
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
||||
|
|
@ -24,7 +24,7 @@ import assert from 'node:assert/strict';
|
|||
|
||||
// --- Preferences helpers (same pattern as doctor-git.test.ts K001) ---
|
||||
|
||||
const RUNNER_PREFS_PATH = join(process.cwd(), ".gsd", "preferences.md");
|
||||
const RUNNER_PREFS_PATH = join(process.cwd(), ".gsd", "PREFERENCES.md");
|
||||
|
||||
function writeRunnerPreferences(isolation: "none" | "worktree" | "branch"): void {
|
||||
mkdirSync(join(process.cwd(), ".gsd"), { recursive: true });
|
||||
|
|
@ -72,12 +72,12 @@ try {
|
|||
|
||||
// Test 4: shouldUseWorktreeIsolation returns false for no prefs (default: none)
|
||||
// Worktree isolation requires explicit opt-in — default is "none" so GSD
|
||||
// works out of the box without preferences.md (#2480).
|
||||
// works out of the box without PREFERENCES.md (#2480).
|
||||
// Skip if global prefs exist — they override the default and this test
|
||||
// cannot control ~/.gsd/preferences.md.
|
||||
// cannot control ~/.gsd/PREFERENCES.md.
|
||||
|
||||
test('shouldUseWorktreeIsolation returns false for no prefs (default: none)', () => {
|
||||
const globalPrefsExist = existsSync(join(homedir(), ".gsd", "preferences.md"))
|
||||
const globalPrefsExist = existsSync(join(homedir(), ".gsd", "PREFERENCES.md"))
|
||||
|| existsSync(join(homedir(), ".gsd", "PREFERENCES.md"));
|
||||
if (!globalPrefsExist) {
|
||||
try {
|
||||
|
|
@ -91,9 +91,9 @@ test('shouldUseWorktreeIsolation returns false for no prefs (default: none)', ()
|
|||
}
|
||||
});
|
||||
|
||||
// Test 5: getIsolationMode returns "none" when no preferences.md exists (#2480)
|
||||
// Test 5: getIsolationMode returns "none" when no PREFERENCES.md exists (#2480)
|
||||
test('getIsolationMode returns "none" with no prefs (default)', () => {
|
||||
const globalPrefsExist = existsSync(join(homedir(), ".gsd", "preferences.md"))
|
||||
const globalPrefsExist = existsSync(join(homedir(), ".gsd", "PREFERENCES.md"))
|
||||
|| existsSync(join(homedir(), ".gsd", "PREFERENCES.md"));
|
||||
if (!globalPrefsExist) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ test("getIsolationMode defaults to none when preferences have no isolation setti
|
|||
// Validate the default via validatePreferences: when no isolation is set,
|
||||
// preferences.git.isolation is undefined, and getIsolationMode returns "none".
|
||||
// Default changed from "worktree" to "none" so GSD works out of the box
|
||||
// without preferences.md (#2480).
|
||||
// without PREFERENCES.md (#2480).
|
||||
const { preferences } = validatePreferences({});
|
||||
assert.equal(preferences.git?.isolation, undefined, "no isolation in empty prefs");
|
||||
const isolation = preferences.git?.isolation;
|
||||
|
|
|
|||
|
|
@ -63,13 +63,13 @@ test("show_token_cost defaults to undefined (disabled) when not set", () => {
|
|||
assert.equal(preferences.show_token_cost, undefined);
|
||||
});
|
||||
|
||||
test("empty preferences.md does not enable show_token_cost", () => {
|
||||
test("empty PREFERENCES.md does not enable show_token_cost", () => {
|
||||
const prefs = parsePreferencesMarkdown("---\nversion: 1\n---\n");
|
||||
assert.ok(prefs);
|
||||
assert.equal(prefs.show_token_cost, undefined);
|
||||
});
|
||||
|
||||
test("preferences.md with show_token_cost: true enables the preference", () => {
|
||||
test("PREFERENCES.md with show_token_cost: true enables the preference", () => {
|
||||
const prefs = parsePreferencesMarkdown("---\nshow_token_cost: true\n---\n");
|
||||
assert.ok(prefs);
|
||||
assert.equal(prefs.show_token_cost, true);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const MAX_NATIVE_SEARCHES_PER_SESSION = 15;
|
|||
|
||||
/** When true, skip native web search injection and keep Brave/custom tools active on Anthropic. */
|
||||
export function preferBraveSearch(): boolean {
|
||||
// preferences.md takes priority over env var
|
||||
// PREFERENCES.md takes priority over env var
|
||||
const prefsPref = resolveSearchProviderFromPreferences();
|
||||
if (prefsPref === "brave" || prefsPref === "tavily" || prefsPref === "ollama") return true;
|
||||
if (prefsPref === "native") return false;
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export function resolveSearchProvider(overridePreference?: string): SearchProvid
|
|||
if (overridePreference && VALID_PREFERENCES.has(overridePreference)) {
|
||||
pref = overridePreference as SearchProviderPreference
|
||||
} else {
|
||||
// preferences.md takes priority over auth.json
|
||||
// PREFERENCES.md takes priority over auth.json
|
||||
const mdPref = resolveSearchProviderFromPreferences()
|
||||
if (mdPref && mdPref !== 'auto' && mdPref !== 'native') {
|
||||
pref = mdPref as SearchProviderPreference
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export async function collectHooksData(projectCwdOverride?: string): Promise<Hoo
|
|||
}
|
||||
|
||||
// getHookStatus() internally calls resolvePostUnitHooks() and resolvePreDispatchHooks()
|
||||
// from preferences.ts, which read from process.cwd()/.gsd/preferences.md.
|
||||
// from preferences.ts, which read from process.cwd()/.gsd/PREFERENCES.md.
|
||||
// We set cwd to projectCwd so preferences resolution finds the right files.
|
||||
// In a cold child process, cycleCounts is empty, so activeCycles will be {}.
|
||||
const script = [
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const NO_STORE = { "Cache-Control": "no-store" } as const
|
|||
// ─── Helpers (same pattern as remote-questions/route.ts) ─────────────────────
|
||||
|
||||
function getPreferencesPath(): string {
|
||||
return join(homedir(), ".gsd", "preferences.md")
|
||||
return join(homedir(), ".gsd", "PREFERENCES.md")
|
||||
}
|
||||
|
||||
function parseFrontmatter(content: string): { data: Record<string, unknown>; body: string } {
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ function maskToken(token: string): string {
|
|||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function getPreferencesPath(): string {
|
||||
return join(homedir(), ".gsd", "preferences.md")
|
||||
return join(homedir(), ".gsd", "PREFERENCES.md")
|
||||
}
|
||||
|
||||
function clamp(value: number | undefined, defaultVal: number, min: number, max: number): number {
|
||||
|
|
|
|||
|
|
@ -1200,7 +1200,7 @@ export function ExperimentalPanel() {
|
|||
{data && (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
Changes are written to{" "}
|
||||
<span className="font-mono">{prefs?.path ?? "~/.gsd/preferences.md"}</span>
|
||||
<span className="font-mono">{prefs?.path ?? "~/.gsd/PREFERENCES.md"}</span>
|
||||
{" "}and take effect on the next session.
|
||||
</p>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue