gsd-pi 2.3.11 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/cli.js +4 -2
- package/dist/pi-migration.d.ts +14 -0
- package/dist/pi-migration.js +57 -0
- package/package.json +2 -2
- package/src/resources/GSD-WORKFLOW.md +7 -7
- package/src/resources/extensions/gsd/auto.ts +78 -23
- package/src/resources/extensions/gsd/docs/preferences-reference.md +27 -0
- package/src/resources/extensions/gsd/git-service.ts +588 -0
- package/src/resources/extensions/gsd/index.ts +11 -6
- package/src/resources/extensions/gsd/preferences.ts +51 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +62 -216
- package/src/resources/extensions/gsd/templates/preferences.md +7 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +1250 -0
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +0 -2
- package/src/resources/extensions/gsd/worktree-command.ts +48 -6
- package/src/resources/extensions/gsd/worktree.ts +40 -147
- package/src/resources/extensions/search-the-web/index.ts +16 -25
- package/src/resources/extensions/search-the-web/native-search.ts +157 -0
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ Both terminals read and write the same `.gsd/` files on disk. Your decisions in
|
|
|
200
200
|
|
|
201
201
|
### First launch
|
|
202
202
|
|
|
203
|
-
On first run, GSD launches a branded setup wizard that walks you through LLM provider selection (OAuth or API key), then optional tool API keys (Brave Search, Context7, Jina, Slack, Discord). Every step is skippable — press Enter to skip any. Run `gsd config` anytime to re-run the wizard.
|
|
203
|
+
On first run, GSD launches a branded setup wizard that walks you through LLM provider selection (OAuth or API key), then optional tool API keys (Brave Search, Context7, Jina, Slack, Discord). Every step is skippable — press Enter to skip any. If you have an existing Pi installation, your provider credentials (LLM and tool keys) are imported automatically. Run `gsd config` anytime to re-run the wizard.
|
|
204
204
|
|
|
205
205
|
### Commands
|
|
206
206
|
|
|
@@ -252,17 +252,18 @@ Branch-per-slice with squash merge. Fully automated.
|
|
|
252
252
|
|
|
253
253
|
```
|
|
254
254
|
main:
|
|
255
|
-
|
|
255
|
+
docs(M001/S04): workflow documentation and examples
|
|
256
|
+
fix(M001/S03): bug fixes and doc corrections
|
|
256
257
|
feat(M001/S02): API endpoints and middleware
|
|
257
258
|
feat(M001/S01): data model and type system
|
|
258
259
|
|
|
259
|
-
gsd/M001/S01 (
|
|
260
|
+
gsd/M001/S01 (deleted after merge):
|
|
260
261
|
feat(S01/T03): file writer with round-trip fidelity
|
|
261
262
|
feat(S01/T02): markdown parser for plan files
|
|
262
263
|
feat(S01/T01): core types and interfaces
|
|
263
264
|
```
|
|
264
265
|
|
|
265
|
-
One commit per slice on main.
|
|
266
|
+
One commit per slice on main. Squash commits are the permanent record — branches are deleted after merge. Git bisect works. Individual slices are revertable.
|
|
266
267
|
|
|
267
268
|
### Verification
|
|
268
269
|
|
package/dist/cli.js
CHANGED
|
@@ -2,9 +2,10 @@ import { AuthStorage, DefaultResourceLoader, ModelRegistry, SettingsManager, Ses
|
|
|
2
2
|
import { existsSync, readdirSync, renameSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { agentDir, sessionsDir, authFilePath } from './app-paths.js';
|
|
5
|
-
import { initResources } from './resource-loader.js';
|
|
5
|
+
import { initResources, buildResourceLoader } from './resource-loader.js';
|
|
6
6
|
import { ensureManagedTools } from './tool-bootstrap.js';
|
|
7
7
|
import { loadStoredEnvKeys } from './wizard.js';
|
|
8
|
+
import { migratePiCredentials } from './pi-migration.js';
|
|
8
9
|
import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
|
|
9
10
|
function parseCliArgs(argv) {
|
|
10
11
|
const flags = { extensions: [], messages: [] };
|
|
@@ -74,6 +75,7 @@ if (cliFlags.messages[0] === 'config') {
|
|
|
74
75
|
ensureManagedTools(join(agentDir, 'bin'));
|
|
75
76
|
const authStorage = AuthStorage.create(authFilePath);
|
|
76
77
|
loadStoredEnvKeys(authStorage);
|
|
78
|
+
migratePiCredentials(authStorage);
|
|
77
79
|
// Run onboarding wizard on first launch (no LLM provider configured)
|
|
78
80
|
if (!isPrintMode && shouldRunOnboarding(authStorage)) {
|
|
79
81
|
await runOnboarding(authStorage);
|
|
@@ -200,7 +202,7 @@ if (existsSync(sessionsDir)) {
|
|
|
200
202
|
}
|
|
201
203
|
const sessionManager = SessionManager.create(cwd, projectSessionsDir);
|
|
202
204
|
initResources(agentDir);
|
|
203
|
-
const resourceLoader =
|
|
205
|
+
const resourceLoader = buildResourceLoader(agentDir);
|
|
204
206
|
await resourceLoader.reload();
|
|
205
207
|
const { session, extensionsResult } = await createAgentSession({
|
|
206
208
|
authStorage,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time migration of provider credentials from Pi (~/.pi/agent/auth.json)
|
|
3
|
+
* into GSD's auth storage. Runs when GSD has no LLM providers configured,
|
|
4
|
+
* so users with an existing Pi install skip re-authentication.
|
|
5
|
+
*/
|
|
6
|
+
import type { AuthStorage } from '@mariozechner/pi-coding-agent';
|
|
7
|
+
/**
|
|
8
|
+
* Migrate provider credentials from Pi's auth.json into GSD's AuthStorage.
|
|
9
|
+
*
|
|
10
|
+
* Only runs when GSD has no LLM provider configured and Pi's auth.json exists.
|
|
11
|
+
* Copies any credentials GSD doesn't already have. Returns true if an LLM
|
|
12
|
+
* provider was migrated (so onboarding can be skipped).
|
|
13
|
+
*/
|
|
14
|
+
export declare function migratePiCredentials(authStorage: AuthStorage): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time migration of provider credentials from Pi (~/.pi/agent/auth.json)
|
|
3
|
+
* into GSD's auth storage. Runs when GSD has no LLM providers configured,
|
|
4
|
+
* so users with an existing Pi install skip re-authentication.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
const PI_AUTH_PATH = join(homedir(), '.pi', 'agent', 'auth.json');
|
|
10
|
+
const LLM_PROVIDER_IDS = [
|
|
11
|
+
'anthropic',
|
|
12
|
+
'openai',
|
|
13
|
+
'github-copilot',
|
|
14
|
+
'openai-codex',
|
|
15
|
+
'google-gemini-cli',
|
|
16
|
+
'google-antigravity',
|
|
17
|
+
'google',
|
|
18
|
+
'groq',
|
|
19
|
+
'xai',
|
|
20
|
+
'openrouter',
|
|
21
|
+
'mistral',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Migrate provider credentials from Pi's auth.json into GSD's AuthStorage.
|
|
25
|
+
*
|
|
26
|
+
* Only runs when GSD has no LLM provider configured and Pi's auth.json exists.
|
|
27
|
+
* Copies any credentials GSD doesn't already have. Returns true if an LLM
|
|
28
|
+
* provider was migrated (so onboarding can be skipped).
|
|
29
|
+
*/
|
|
30
|
+
export function migratePiCredentials(authStorage) {
|
|
31
|
+
try {
|
|
32
|
+
// Only migrate when GSD has no LLM providers
|
|
33
|
+
const existing = authStorage.list();
|
|
34
|
+
const hasLlm = existing.some(id => LLM_PROVIDER_IDS.includes(id));
|
|
35
|
+
if (hasLlm)
|
|
36
|
+
return false;
|
|
37
|
+
if (!existsSync(PI_AUTH_PATH))
|
|
38
|
+
return false;
|
|
39
|
+
const raw = readFileSync(PI_AUTH_PATH, 'utf-8');
|
|
40
|
+
const piData = JSON.parse(raw);
|
|
41
|
+
let migratedLlm = false;
|
|
42
|
+
for (const [providerId, credential] of Object.entries(piData)) {
|
|
43
|
+
if (authStorage.has(providerId))
|
|
44
|
+
continue;
|
|
45
|
+
authStorage.set(providerId, credential);
|
|
46
|
+
const isLlm = LLM_PROVIDER_IDS.includes(providerId);
|
|
47
|
+
if (isLlm)
|
|
48
|
+
migratedLlm = true;
|
|
49
|
+
process.stderr.write(`[gsd] Migrated ${isLlm ? 'LLM provider' : 'credential'}: ${providerId} (from Pi)\n`);
|
|
50
|
+
}
|
|
51
|
+
return migratedLlm;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Non-fatal — don't block startup
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-pi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "GSD — Get Shit Done coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsc && npm run copy-themes",
|
|
37
37
|
"copy-themes": "node -e \"const{mkdirSync,cpSync}=require('fs');const{resolve}=require('path');const src=resolve(__dirname,'node_modules/@mariozechner/pi-coding-agent/dist/modes/interactive/theme');mkdirSync('pkg/dist/modes/interactive/theme',{recursive:true});cpSync(src,'pkg/dist/modes/interactive/theme',{recursive:true})\"",
|
|
38
|
-
"test": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test
|
|
38
|
+
"test": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts",
|
|
39
39
|
"dev": "tsc --watch",
|
|
40
40
|
"postinstall": "node scripts/postinstall.js",
|
|
41
41
|
"pi:install-global": "node scripts/install-pi-global.js",
|
|
@@ -548,7 +548,7 @@ If files disagree, **pause and surface to the user**:
|
|
|
548
548
|
1. **Slice starts** → create branch `gsd/M001/S01` from main
|
|
549
549
|
2. **Per-task commits** on the branch — atomic, descriptive, bisectable
|
|
550
550
|
3. **Slice completes** → squash merge to main as one clean commit
|
|
551
|
-
4. **Branch
|
|
551
|
+
4. **Branch deleted** — squash commit on main is the permanent record
|
|
552
552
|
|
|
553
553
|
### What Main Looks Like
|
|
554
554
|
|
|
@@ -566,21 +566,21 @@ One commit per slice. Individually revertable. Reads like a changelog.
|
|
|
566
566
|
gsd/M001/S01:
|
|
567
567
|
test(S01): round-trip tests passing
|
|
568
568
|
feat(S01/T03): file writer with round-trip fidelity
|
|
569
|
-
|
|
569
|
+
chore(S01/T03): auto-commit after task
|
|
570
570
|
feat(S01/T02): markdown parser for plan files
|
|
571
|
-
|
|
571
|
+
chore(S01/T02): auto-commit after task
|
|
572
572
|
feat(S01/T01): core types and interfaces
|
|
573
|
-
|
|
573
|
+
chore(S01/T01): auto-commit after task
|
|
574
574
|
```
|
|
575
575
|
|
|
576
576
|
### Commit Conventions
|
|
577
577
|
|
|
578
578
|
| When | Format | Example |
|
|
579
579
|
|------|--------|---------|
|
|
580
|
-
|
|
|
580
|
+
| Auto-commit (dirty state) | `chore(S01/T02): auto-commit after task` | Automatic save of work in progress |
|
|
581
581
|
| After task verified | `feat(S01/T02): <what was built>` | The real work |
|
|
582
582
|
| Plan/docs committed | `docs(S01): add slice plan` | Bundled with first task |
|
|
583
|
-
| Slice squash to main | `
|
|
583
|
+
| Slice squash to main | `type(M001/S01): <slice title>` | Type inferred from title (`feat`, `fix`, `docs`, etc.) |
|
|
584
584
|
|
|
585
585
|
Commit types: `feat`, `fix`, `test`, `refactor`, `docs`, `chore`
|
|
586
586
|
|
|
@@ -601,7 +601,7 @@ Tasks completed:
|
|
|
601
601
|
|
|
602
602
|
| Problem | Fix |
|
|
603
603
|
|---------|-----|
|
|
604
|
-
| Bad task | `git reset --hard` to
|
|
604
|
+
| Bad task | `git reset --hard HEAD~1` to previous commit on the branch |
|
|
605
605
|
| Bad slice | `git revert <squash commit>` on main |
|
|
606
606
|
| UAT failure after merge | Fix tasks on `gsd/M001/S01-fix` branch, squash as `fix(M001/S01): <fix>` |
|
|
607
607
|
|
|
@@ -62,11 +62,12 @@ import {
|
|
|
62
62
|
ensureSliceBranch,
|
|
63
63
|
getCurrentBranch,
|
|
64
64
|
getMainBranch,
|
|
65
|
-
getSliceBranchName,
|
|
66
65
|
parseSliceBranch,
|
|
67
66
|
switchToMain,
|
|
68
67
|
mergeSliceToMain,
|
|
69
68
|
} from "./worktree.ts";
|
|
69
|
+
import { GitServiceImpl } from "./git-service.ts";
|
|
70
|
+
import type { GitPreferences } from "./git-service.ts";
|
|
70
71
|
import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
71
72
|
import { makeUI, GLYPH, INDENT } from "../shared/ui.js";
|
|
72
73
|
import { showNextAction } from "../shared/next-action-ui.js";
|
|
@@ -93,6 +94,18 @@ function persistCompletedKey(base: string, key: string): void {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/** Remove a stale completed unit key from disk. */
|
|
98
|
+
function removePersistedKey(base: string, key: string): void {
|
|
99
|
+
const file = completedKeysPath(base);
|
|
100
|
+
try {
|
|
101
|
+
if (existsSync(file)) {
|
|
102
|
+
let keys: string[] = JSON.parse(readFileSync(file, "utf-8"));
|
|
103
|
+
keys = keys.filter(k => k !== key);
|
|
104
|
+
writeFileSync(file, JSON.stringify(keys), "utf-8");
|
|
105
|
+
}
|
|
106
|
+
} catch { /* non-fatal */ }
|
|
107
|
+
}
|
|
108
|
+
|
|
96
109
|
/** Load all completed unit keys from disk into the in-memory set. */
|
|
97
110
|
function loadPersistedKeys(base: string, target: Set<string>): void {
|
|
98
111
|
const file = completedKeysPath(base);
|
|
@@ -112,6 +125,7 @@ let stepMode = false;
|
|
|
112
125
|
let verbose = false;
|
|
113
126
|
let cmdCtx: ExtensionCommandContext | null = null;
|
|
114
127
|
let basePath = "";
|
|
128
|
+
let gitService: GitServiceImpl | null = null;
|
|
115
129
|
|
|
116
130
|
/** Track total dispatches per unit to detect stuck loops (catches A→B→A→B patterns) */
|
|
117
131
|
const unitDispatchCount = new Map<string, number>();
|
|
@@ -376,6 +390,9 @@ export async function startAuto(
|
|
|
376
390
|
} catch { /* nothing to commit */ }
|
|
377
391
|
}
|
|
378
392
|
|
|
393
|
+
// Initialize GitServiceImpl — basePath is set and git repo confirmed
|
|
394
|
+
gitService = new GitServiceImpl(basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
|
|
395
|
+
|
|
379
396
|
// Check for crash from previous session
|
|
380
397
|
const crashLock = readCrashLock(base);
|
|
381
398
|
if (crashLock) {
|
|
@@ -1190,15 +1207,27 @@ async function dispatchNextUnit(
|
|
|
1190
1207
|
// Idempotency: skip units already completed in a prior session.
|
|
1191
1208
|
const idempotencyKey = `${unitType}/${unitId}`;
|
|
1192
1209
|
if (completedKeySet.has(idempotencyKey)) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1210
|
+
// Cross-validate: does the expected artifact actually exist?
|
|
1211
|
+
const artifactExists = verifyExpectedArtifact(unitType, unitId, basePath);
|
|
1212
|
+
if (artifactExists) {
|
|
1213
|
+
ctx.ui.notify(
|
|
1214
|
+
`Skipping ${unitType} ${unitId} — already completed in a prior session. Advancing.`,
|
|
1215
|
+
"info",
|
|
1216
|
+
);
|
|
1217
|
+
// Yield to the event loop before re-dispatching to avoid tight recursion
|
|
1218
|
+
// when many units are already completed (e.g., after crash recovery).
|
|
1219
|
+
await new Promise(r => setImmediate(r));
|
|
1220
|
+
await dispatchNextUnit(ctx, pi);
|
|
1221
|
+
return;
|
|
1222
|
+
} else {
|
|
1223
|
+
// Stale completion record — artifact missing. Remove and re-run.
|
|
1224
|
+
completedKeySet.delete(idempotencyKey);
|
|
1225
|
+
removePersistedKey(basePath, idempotencyKey);
|
|
1226
|
+
ctx.ui.notify(
|
|
1227
|
+
`Re-running ${unitType} ${unitId} — marked complete but expected artifact missing.`,
|
|
1228
|
+
"warning",
|
|
1229
|
+
);
|
|
1230
|
+
}
|
|
1202
1231
|
}
|
|
1203
1232
|
|
|
1204
1233
|
// Stuck detection — tracks total dispatches per unit (not just consecutive repeats).
|
|
@@ -1234,20 +1263,26 @@ async function dispatchNextUnit(
|
|
|
1234
1263
|
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId);
|
|
1235
1264
|
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
|
|
1236
1265
|
|
|
1237
|
-
//
|
|
1266
|
+
// Only mark the previous unit as completed if:
|
|
1267
|
+
// 1. We're not about to re-dispatch the same unit (retry scenario)
|
|
1268
|
+
// 2. The expected artifact actually exists on disk
|
|
1238
1269
|
const closeoutKey = `${currentUnit.type}/${currentUnit.id}`;
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1270
|
+
const incomingKey = `${unitType}/${unitId}`;
|
|
1271
|
+
const artifactVerified = verifyExpectedArtifact(currentUnit.type, currentUnit.id, basePath);
|
|
1272
|
+
if (closeoutKey !== incomingKey && artifactVerified) {
|
|
1273
|
+
persistCompletedKey(basePath, closeoutKey);
|
|
1274
|
+
completedKeySet.add(closeoutKey);
|
|
1275
|
+
|
|
1276
|
+
completedUnits.push({
|
|
1277
|
+
type: currentUnit.type,
|
|
1278
|
+
id: currentUnit.id,
|
|
1279
|
+
startedAt: currentUnit.startedAt,
|
|
1280
|
+
finishedAt: Date.now(),
|
|
1281
|
+
});
|
|
1282
|
+
clearUnitRuntimeRecord(basePath, currentUnit.type, currentUnit.id);
|
|
1283
|
+
unitDispatchCount.delete(`${currentUnit.type}/${currentUnit.id}`);
|
|
1284
|
+
unitRecoveryCount.delete(`${currentUnit.type}/${currentUnit.id}`);
|
|
1285
|
+
}
|
|
1251
1286
|
}
|
|
1252
1287
|
currentUnit = { type: unitType, id: unitId, startedAt: Date.now() };
|
|
1253
1288
|
writeUnitRuntimeRecord(basePath, unitType, unitId, currentUnit.startedAt, {
|
|
@@ -2587,6 +2622,15 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
2587
2622
|
const dir = resolveSlicePath(base, mid, sid!);
|
|
2588
2623
|
return dir ? join(dir, buildSliceFileName(sid!, "UAT-RESULT")) : null;
|
|
2589
2624
|
}
|
|
2625
|
+
case "execute-task": {
|
|
2626
|
+
const tid = parts[2];
|
|
2627
|
+
const dir = resolveSlicePath(base, mid, sid!);
|
|
2628
|
+
return dir && tid ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY")) : null;
|
|
2629
|
+
}
|
|
2630
|
+
case "complete-slice": {
|
|
2631
|
+
const dir = resolveSlicePath(base, mid, sid!);
|
|
2632
|
+
return dir ? join(dir, buildSliceFileName(sid!, "SUMMARY")) : null;
|
|
2633
|
+
}
|
|
2590
2634
|
case "complete-milestone": {
|
|
2591
2635
|
const dir = resolveMilestonePath(base, mid);
|
|
2592
2636
|
return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
|
|
@@ -2596,6 +2640,17 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
2596
2640
|
}
|
|
2597
2641
|
}
|
|
2598
2642
|
|
|
2643
|
+
/**
|
|
2644
|
+
* Check whether the expected artifact for a unit exists on disk.
|
|
2645
|
+
* Returns true if the artifact file exists, or if the unit type has no
|
|
2646
|
+
* single verifiable artifact (e.g., replan-slice).
|
|
2647
|
+
*/
|
|
2648
|
+
function verifyExpectedArtifact(unitType: string, unitId: string, base: string): boolean {
|
|
2649
|
+
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
2650
|
+
if (!absPath) return true;
|
|
2651
|
+
return existsSync(absPath);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2599
2654
|
/**
|
|
2600
2655
|
* Write a placeholder artifact so the pipeline can advance past a stuck unit.
|
|
2601
2656
|
* Returns the relative path written, or null if the path couldn't be resolved.
|
|
@@ -40,6 +40,14 @@ Full documentation for `~/.gsd/preferences.md` (global) and `.gsd/preferences.md
|
|
|
40
40
|
- `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10).
|
|
41
41
|
- `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30).
|
|
42
42
|
|
|
43
|
+
- `git`: configures GSD's git behavior. All fields are optional — omit any to use defaults. Keys:
|
|
44
|
+
- `auto_push`: boolean — automatically push commits to the remote after committing. Default: `false`.
|
|
45
|
+
- `push_branches`: boolean — push newly created slice branches to the remote. Default: `false`.
|
|
46
|
+
- `remote`: string — git remote name to push to. Default: `"origin"`.
|
|
47
|
+
- `snapshots`: boolean — create snapshot commits (WIP saves) during long-running tasks. Default: `false`.
|
|
48
|
+
- `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a slice branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `false`.
|
|
49
|
+
- `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content.
|
|
50
|
+
|
|
43
51
|
---
|
|
44
52
|
|
|
45
53
|
## Best Practices
|
|
@@ -101,3 +109,22 @@ skill_rules:
|
|
|
101
109
|
- find-skills
|
|
102
110
|
---
|
|
103
111
|
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Git Preferences Example
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
---
|
|
119
|
+
version: 1
|
|
120
|
+
git:
|
|
121
|
+
auto_push: true
|
|
122
|
+
push_branches: true
|
|
123
|
+
remote: origin
|
|
124
|
+
snapshots: true
|
|
125
|
+
pre_merge_check: auto
|
|
126
|
+
commit_type: feat
|
|
127
|
+
---
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
All git fields are optional. Omit any field to use the default behavior. Project-level preferences override global preferences on a per-field basis.
|