gsd-pi 2.42.0-dev.97e9e30 → 2.42.0-dev.eedc83f
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 +23 -0
- package/dist/cli.js +15 -1
- package/dist/resource-loader.js +39 -6
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
- package/dist/resources/extensions/gsd/auto-prompts.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
- package/dist/resources/extensions/gsd/detection.js +19 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
- package/dist/resources/extensions/gsd/forensics.js +84 -0
- package/dist/resources/extensions/gsd/git-constants.js +1 -0
- package/dist/resources/extensions/gsd/git-service.js +68 -2
- package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +59 -8
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/repo-identity.js +46 -5
- package/dist/resources/extensions/gsd/service-tier.js +13 -4
- package/dist/resources/extensions/gsd/session-lock.js +2 -2
- package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
- package/dist/resources/extensions/mcp-client/index.js +2 -1
- package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +40 -4
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +6 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +53 -0
- package/packages/pi-agent-core/src/agent.ts +3 -0
- package/packages/pi-agent-core/src/types.ts +6 -0
- package/packages/pi-agent-core/tsconfig.json +1 -1
- package/packages/pi-ai/dist/models.d.ts +5 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +1135 -1588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +1543 -0
- package/packages/pi-ai/src/models.generated.ts +1140 -1593
- package/packages/pi-ai/src/models.ts +7 -4
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
- package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
- package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
- package/src/resources/extensions/gsd/auto-prompts.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
- package/src/resources/extensions/gsd/detection.ts +19 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
- package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/forensics.ts +92 -0
- package/src/resources/extensions/gsd/git-constants.ts +1 -0
- package/src/resources/extensions/gsd/git-service.ts +71 -2
- package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +62 -6
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/repo-identity.ts +48 -5
- package/src/resources/extensions/gsd/service-tier.ts +17 -4
- package/src/resources/extensions/gsd/session-lock.ts +2 -2
- package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
- package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
- package/src/resources/extensions/gsd/worktree-resolver.ts +2 -2
- package/src/resources/extensions/mcp-client/index.ts +5 -1
- package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
- /package/dist/web/standalone/.next/static/{PXrI5DoWsm7rwAVnEU2rD → JUBX5FUR73jiViQU5a-Cx}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{PXrI5DoWsm7rwAVnEU2rD → JUBX5FUR73jiViQU5a-Cx}/_ssgManifest.js +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Uses temp directories with real .gsd/milestones/M001/ structure.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import test from 'node:test';
|
|
11
|
+
import { describe, test, beforeEach, afterEach } from 'node:test';
|
|
12
12
|
import assert from 'node:assert/strict';
|
|
13
13
|
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
14
14
|
import { join } from 'node:path';
|
|
@@ -30,12 +30,21 @@ function writeManifest(base: string, content: string): void {
|
|
|
30
30
|
|
|
31
31
|
// ─── Mixed statuses ──────────────────────────────────────────────────────────
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
describe('getManifestStatus: mixed statuses', () => {
|
|
34
|
+
let tmp: string;
|
|
35
|
+
let savedVal: string | undefined;
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
tmp = makeTempDir('manifest-mixed');
|
|
38
|
+
savedVal = process.env.GSD_TEST_EXISTING_KEY_001;
|
|
37
39
|
process.env.GSD_TEST_EXISTING_KEY_001 = 'some-value';
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
delete process.env.GSD_TEST_EXISTING_KEY_001;
|
|
43
|
+
if (savedVal !== undefined) process.env.GSD_TEST_EXISTING_KEY_001 = savedVal;
|
|
44
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
45
|
+
});
|
|
38
46
|
|
|
47
|
+
test('categorizes entries correctly', async () => {
|
|
39
48
|
writeManifest(tmp, `# Secrets Manifest
|
|
40
49
|
|
|
41
50
|
**Milestone:** M001
|
|
@@ -80,18 +89,17 @@ test('getManifestStatus: mixed statuses — categorizes entries correctly', asyn
|
|
|
80
89
|
assert.deepStrictEqual(result!.collected, ['COLLECTED_KEY']);
|
|
81
90
|
assert.deepStrictEqual(result!.skipped, ['SKIPPED_KEY']);
|
|
82
91
|
assert.deepStrictEqual(result!.existing, ['GSD_TEST_EXISTING_KEY_001']);
|
|
83
|
-
}
|
|
84
|
-
delete process.env.GSD_TEST_EXISTING_KEY_001;
|
|
85
|
-
if (savedVal !== undefined) process.env.GSD_TEST_EXISTING_KEY_001 = savedVal;
|
|
86
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
87
|
-
}
|
|
92
|
+
});
|
|
88
93
|
});
|
|
89
94
|
|
|
90
95
|
// ─── All pending ─────────────────────────────────────────────────────────────
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
describe('getManifestStatus: simple temp dir tests', () => {
|
|
98
|
+
let tmp: string;
|
|
99
|
+
beforeEach(() => { tmp = makeTempDir('manifest-test'); });
|
|
100
|
+
afterEach(() => { rmSync(tmp, { recursive: true, force: true }); });
|
|
101
|
+
|
|
102
|
+
test('all pending — 3 pending entries, none in env', async () => {
|
|
95
103
|
// Ensure none of these are in process.env
|
|
96
104
|
delete process.env.PEND_A;
|
|
97
105
|
delete process.env.PEND_B;
|
|
@@ -133,16 +141,11 @@ test('getManifestStatus: all pending — 3 pending entries, none in env', async
|
|
|
133
141
|
assert.deepStrictEqual(result!.collected, []);
|
|
134
142
|
assert.deepStrictEqual(result!.skipped, []);
|
|
135
143
|
assert.deepStrictEqual(result!.existing, []);
|
|
136
|
-
}
|
|
137
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
138
|
-
}
|
|
139
|
-
});
|
|
144
|
+
});
|
|
140
145
|
|
|
141
|
-
// ─── All collected ───────────────────────────────────────────────────────────
|
|
146
|
+
// ─── All collected ───────────────────────────────────────────────────────────
|
|
142
147
|
|
|
143
|
-
test('
|
|
144
|
-
const tmp = makeTempDir('manifest-collected');
|
|
145
|
-
try {
|
|
148
|
+
test('all collected — 2 collected entries, none in env', async () => {
|
|
146
149
|
delete process.env.COLL_X;
|
|
147
150
|
delete process.env.COLL_Y;
|
|
148
151
|
|
|
@@ -174,64 +177,19 @@ test('getManifestStatus: all collected — 2 collected entries, none in env', as
|
|
|
174
177
|
assert.deepStrictEqual(result!.collected, ['COLL_X', 'COLL_Y']);
|
|
175
178
|
assert.deepStrictEqual(result!.skipped, []);
|
|
176
179
|
assert.deepStrictEqual(result!.existing, []);
|
|
177
|
-
}
|
|
178
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// ─── Key in env overrides manifest status ────────────────────────────────────
|
|
183
|
-
|
|
184
|
-
test('getManifestStatus: key in env overrides manifest status — collected key in env goes to existing', async () => {
|
|
185
|
-
const tmp = makeTempDir('manifest-override');
|
|
186
|
-
const savedVal = process.env.GSD_TEST_OVERRIDE_KEY;
|
|
187
|
-
try {
|
|
188
|
-
process.env.GSD_TEST_OVERRIDE_KEY = 'already-here';
|
|
189
|
-
|
|
190
|
-
writeManifest(tmp, `# Secrets Manifest
|
|
191
|
-
|
|
192
|
-
**Milestone:** M001
|
|
193
|
-
**Generated:** 2025-06-20T10:00:00Z
|
|
194
|
-
|
|
195
|
-
### GSD_TEST_OVERRIDE_KEY
|
|
196
|
-
|
|
197
|
-
**Service:** Override
|
|
198
|
-
**Status:** collected
|
|
199
|
-
**Destination:** dotenv
|
|
200
|
-
|
|
201
|
-
1. Was collected but now in env
|
|
202
|
-
`);
|
|
203
|
-
|
|
204
|
-
const result = await getManifestStatus(tmp, 'M001');
|
|
205
|
-
assert.notStrictEqual(result, null);
|
|
206
|
-
assert.deepStrictEqual(result!.pending, []);
|
|
207
|
-
assert.deepStrictEqual(result!.collected, []);
|
|
208
|
-
assert.deepStrictEqual(result!.skipped, []);
|
|
209
|
-
assert.deepStrictEqual(result!.existing, ['GSD_TEST_OVERRIDE_KEY']);
|
|
210
|
-
} finally {
|
|
211
|
-
delete process.env.GSD_TEST_OVERRIDE_KEY;
|
|
212
|
-
if (savedVal !== undefined) process.env.GSD_TEST_OVERRIDE_KEY = savedVal;
|
|
213
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
214
|
-
}
|
|
215
|
-
});
|
|
180
|
+
});
|
|
216
181
|
|
|
217
|
-
// ─── Missing manifest ────────────────────────────────────────────────────────
|
|
182
|
+
// ─── Missing manifest ────────────────────────────────────────────────────────
|
|
218
183
|
|
|
219
|
-
test('
|
|
220
|
-
const tmp = makeTempDir('manifest-missing');
|
|
221
|
-
try {
|
|
184
|
+
test('missing manifest — returns null', async () => {
|
|
222
185
|
// No .gsd directory at all
|
|
223
186
|
const result = await getManifestStatus(tmp, 'M001');
|
|
224
187
|
assert.strictEqual(result, null);
|
|
225
|
-
}
|
|
226
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
227
|
-
}
|
|
228
|
-
});
|
|
188
|
+
});
|
|
229
189
|
|
|
230
|
-
// ─── Empty manifest (no entries) ─────────────────────────────────────────────
|
|
190
|
+
// ─── Empty manifest (no entries) ─────────────────────────────────────────────
|
|
231
191
|
|
|
232
|
-
test('
|
|
233
|
-
const tmp = makeTempDir('manifest-empty');
|
|
234
|
-
try {
|
|
192
|
+
test('empty manifest — exists but no H3 sections', async () => {
|
|
235
193
|
writeManifest(tmp, `# Secrets Manifest
|
|
236
194
|
|
|
237
195
|
**Milestone:** M001
|
|
@@ -244,16 +202,11 @@ test('getManifestStatus: empty manifest — exists but no H3 sections', async ()
|
|
|
244
202
|
assert.deepStrictEqual(result!.collected, []);
|
|
245
203
|
assert.deepStrictEqual(result!.skipped, []);
|
|
246
204
|
assert.deepStrictEqual(result!.existing, []);
|
|
247
|
-
}
|
|
248
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
249
|
-
}
|
|
250
|
-
});
|
|
205
|
+
});
|
|
251
206
|
|
|
252
|
-
// ─── Env via .env file (not just process.env) ────────────────────────────────
|
|
207
|
+
// ─── Env via .env file (not just process.env) ────────────────────────────────
|
|
253
208
|
|
|
254
|
-
test('
|
|
255
|
-
const tmp = makeTempDir('manifest-dotenv');
|
|
256
|
-
try {
|
|
209
|
+
test('key in .env file counts as existing', async () => {
|
|
257
210
|
delete process.env.DOTENV_ONLY_KEY;
|
|
258
211
|
|
|
259
212
|
writeManifest(tmp, `# Secrets Manifest
|
|
@@ -277,7 +230,45 @@ test('getManifestStatus: key in .env file counts as existing', async () => {
|
|
|
277
230
|
assert.notStrictEqual(result, null);
|
|
278
231
|
assert.deepStrictEqual(result!.existing, ['DOTENV_ONLY_KEY']);
|
|
279
232
|
assert.deepStrictEqual(result!.pending, []);
|
|
280
|
-
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// ─── Key in env overrides manifest status ────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
describe('getManifestStatus: key in env overrides manifest status', () => {
|
|
239
|
+
let tmp: string;
|
|
240
|
+
let savedVal: string | undefined;
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
tmp = makeTempDir('manifest-override');
|
|
243
|
+
savedVal = process.env.GSD_TEST_OVERRIDE_KEY;
|
|
244
|
+
process.env.GSD_TEST_OVERRIDE_KEY = 'already-here';
|
|
245
|
+
});
|
|
246
|
+
afterEach(() => {
|
|
247
|
+
delete process.env.GSD_TEST_OVERRIDE_KEY;
|
|
248
|
+
if (savedVal !== undefined) process.env.GSD_TEST_OVERRIDE_KEY = savedVal;
|
|
281
249
|
rmSync(tmp, { recursive: true, force: true });
|
|
282
|
-
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('collected key in env goes to existing', async () => {
|
|
253
|
+
writeManifest(tmp, `# Secrets Manifest
|
|
254
|
+
|
|
255
|
+
**Milestone:** M001
|
|
256
|
+
**Generated:** 2025-06-20T10:00:00Z
|
|
257
|
+
|
|
258
|
+
### GSD_TEST_OVERRIDE_KEY
|
|
259
|
+
|
|
260
|
+
**Service:** Override
|
|
261
|
+
**Status:** collected
|
|
262
|
+
**Destination:** dotenv
|
|
263
|
+
|
|
264
|
+
1. Was collected but now in env
|
|
265
|
+
`);
|
|
266
|
+
|
|
267
|
+
const result = await getManifestStatus(tmp, 'M001');
|
|
268
|
+
assert.notStrictEqual(result, null);
|
|
269
|
+
assert.deepStrictEqual(result!.pending, []);
|
|
270
|
+
assert.deepStrictEqual(result!.collected, []);
|
|
271
|
+
assert.deepStrictEqual(result!.skipped, []);
|
|
272
|
+
assert.deepStrictEqual(result!.existing, ['GSD_TEST_OVERRIDE_KEY']);
|
|
273
|
+
});
|
|
283
274
|
});
|
|
@@ -4,8 +4,8 @@ import assert from "node:assert/strict";
|
|
|
4
4
|
import {
|
|
5
5
|
supportsServiceTier,
|
|
6
6
|
formatServiceTierStatus,
|
|
7
|
+
formatServiceTierFooterStatus,
|
|
7
8
|
resolveServiceTierIcon,
|
|
8
|
-
type ServiceTierSetting,
|
|
9
9
|
} from "../service-tier.ts";
|
|
10
10
|
|
|
11
11
|
// ─── supportsServiceTier ─────────────────────────────────────────────────────
|
|
@@ -27,6 +27,14 @@ describe("supportsServiceTier", () => {
|
|
|
27
27
|
assert.equal(supportsServiceTier("openai/gpt-5.4"), true);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("returns true for vibeproxy-openai/gpt-5.4 (proxy provider-prefixed)", () => {
|
|
31
|
+
assert.equal(supportsServiceTier("vibeproxy-openai/gpt-5.4"), true);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("returns false for provider-only identifier without gpt-5.4 model suffix", () => {
|
|
35
|
+
assert.equal(supportsServiceTier("vibeproxy-openai"), false);
|
|
36
|
+
});
|
|
37
|
+
|
|
30
38
|
test("returns false for claude-opus-4-6", () => {
|
|
31
39
|
assert.equal(supportsServiceTier("claude-opus-4-6"), false);
|
|
32
40
|
});
|
|
@@ -52,6 +60,11 @@ describe("formatServiceTierStatus", () => {
|
|
|
52
60
|
assert.ok(output.includes("disabled"), `Expected 'disabled' in: ${output}`);
|
|
53
61
|
});
|
|
54
62
|
|
|
63
|
+
test("mentions provider-agnostic model gating", () => {
|
|
64
|
+
const output = formatServiceTierStatus("priority");
|
|
65
|
+
assert.ok(output.includes("regardless of provider"), `Expected provider note in: ${output}`);
|
|
66
|
+
});
|
|
67
|
+
|
|
55
68
|
test("shows priority when set to priority", () => {
|
|
56
69
|
const output = formatServiceTierStatus("priority");
|
|
57
70
|
assert.ok(output.includes("priority"), `Expected 'priority' in: ${output}`);
|
|
@@ -63,6 +76,22 @@ describe("formatServiceTierStatus", () => {
|
|
|
63
76
|
});
|
|
64
77
|
});
|
|
65
78
|
|
|
79
|
+
// ─── formatServiceTierFooterStatus ───────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
describe("formatServiceTierFooterStatus", () => {
|
|
82
|
+
test("returns priority footer status for supported model", () => {
|
|
83
|
+
assert.equal(formatServiceTierFooterStatus("priority", "vibeproxy-openai/gpt-5.4"), "fast: ⚡ priority");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns undefined for unsupported model", () => {
|
|
87
|
+
assert.equal(formatServiceTierFooterStatus("priority", "claude-opus-4-6"), undefined);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("returns undefined when tier is disabled", () => {
|
|
91
|
+
assert.equal(formatServiceTierFooterStatus(undefined, "gpt-5.4"), undefined);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
66
95
|
// ─── resolveServiceTierIcon ──────────────────────────────────────────────────
|
|
67
96
|
|
|
68
97
|
describe("resolveServiceTierIcon", () => {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for macOS numbered symlink variant cleanup (#2205).
|
|
3
|
+
*
|
|
4
|
+
* macOS can rename `.gsd` to `.gsd 2`, `.gsd 3`, etc. when a directory
|
|
5
|
+
* already exists at the target path. ensureGsdSymlink() must detect and
|
|
6
|
+
* remove these numbered variants so the real `.gsd` symlink is always
|
|
7
|
+
* the one in use.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
lstatSync,
|
|
16
|
+
realpathSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
symlinkSync,
|
|
19
|
+
readlinkSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { execSync } from "node:child_process";
|
|
24
|
+
|
|
25
|
+
import { ensureGsdSymlink, externalGsdRoot } from "../repo-identity.ts";
|
|
26
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
27
|
+
|
|
28
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
29
|
+
|
|
30
|
+
function run(command: string, cwd: string): string {
|
|
31
|
+
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function main(): Promise<void> {
|
|
35
|
+
const base = realpathSync(mkdtempSync(join(tmpdir(), "gsd-symlink-variants-")));
|
|
36
|
+
const stateDir = realpathSync(mkdtempSync(join(tmpdir(), "gsd-state-variants-")));
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
process.env.GSD_STATE_DIR = stateDir;
|
|
40
|
+
|
|
41
|
+
// Set up a minimal git repo
|
|
42
|
+
run("git init -b main", base);
|
|
43
|
+
run('git config user.name "Pi Test"', base);
|
|
44
|
+
run('git config user.email "pi@example.com"', base);
|
|
45
|
+
run('git remote add origin git@github.com:example/repo.git', base);
|
|
46
|
+
writeFileSync(join(base, "README.md"), "# Test Repo\n", "utf-8");
|
|
47
|
+
run("git add README.md", base);
|
|
48
|
+
run('git commit -m "chore: init"', base);
|
|
49
|
+
|
|
50
|
+
const externalPath = externalGsdRoot(base);
|
|
51
|
+
|
|
52
|
+
// ── Test: numbered variant directories are cleaned up ──────────────
|
|
53
|
+
console.log("\n=== ensureGsdSymlink removes numbered .gsd variants (#2205) ===");
|
|
54
|
+
{
|
|
55
|
+
// Simulate macOS creating numbered variants: ".gsd 2", ".gsd 3"
|
|
56
|
+
mkdirSync(join(base, ".gsd 2"), { recursive: true });
|
|
57
|
+
mkdirSync(join(base, ".gsd 3"), { recursive: true });
|
|
58
|
+
mkdirSync(join(base, ".gsd 4"), { recursive: true });
|
|
59
|
+
|
|
60
|
+
const result = ensureGsdSymlink(base);
|
|
61
|
+
assertEq(result, externalPath, "ensureGsdSymlink returns external path");
|
|
62
|
+
assertTrue(existsSync(join(base, ".gsd")), ".gsd exists after ensureGsdSymlink");
|
|
63
|
+
assertTrue(lstatSync(join(base, ".gsd")).isSymbolicLink(), ".gsd is a symlink");
|
|
64
|
+
|
|
65
|
+
// The numbered variants must have been removed
|
|
66
|
+
assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" directory was cleaned up');
|
|
67
|
+
assertTrue(!existsSync(join(base, ".gsd 3")), '".gsd 3" directory was cleaned up');
|
|
68
|
+
assertTrue(!existsSync(join(base, ".gsd 4")), '".gsd 4" directory was cleaned up');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Test: numbered variant symlinks are cleaned up ─────────────────
|
|
72
|
+
console.log("\n=== ensureGsdSymlink removes numbered symlink variants ===");
|
|
73
|
+
{
|
|
74
|
+
// Clean slate
|
|
75
|
+
rmSync(join(base, ".gsd"), { recursive: true, force: true });
|
|
76
|
+
|
|
77
|
+
// Simulate: ".gsd 2" is a symlink to the correct target (the real .gsd)
|
|
78
|
+
// and ".gsd" doesn't exist — this is the actual macOS scenario
|
|
79
|
+
const staleTarget = join(stateDir, "projects", "stale-target");
|
|
80
|
+
mkdirSync(staleTarget, { recursive: true });
|
|
81
|
+
symlinkSync(externalPath, join(base, ".gsd 2"), "junction");
|
|
82
|
+
symlinkSync(staleTarget, join(base, ".gsd 3"), "junction");
|
|
83
|
+
|
|
84
|
+
const result = ensureGsdSymlink(base);
|
|
85
|
+
assertEq(result, externalPath, "ensureGsdSymlink returns external path when variants exist");
|
|
86
|
+
assertTrue(existsSync(join(base, ".gsd")), ".gsd exists");
|
|
87
|
+
assertTrue(lstatSync(join(base, ".gsd")).isSymbolicLink(), ".gsd is a symlink");
|
|
88
|
+
|
|
89
|
+
assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" symlink variant was cleaned up');
|
|
90
|
+
assertTrue(!existsSync(join(base, ".gsd 3")), '".gsd 3" symlink variant was cleaned up');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Test: real .gsd directory blocks symlink, but variants still cleaned ──
|
|
94
|
+
console.log("\n=== ensureGsdSymlink cleans variants even when .gsd is a real directory ===");
|
|
95
|
+
{
|
|
96
|
+
// Clean slate
|
|
97
|
+
rmSync(join(base, ".gsd"), { recursive: true, force: true });
|
|
98
|
+
|
|
99
|
+
// .gsd is a real directory (git-tracked) and numbered variants exist
|
|
100
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
101
|
+
writeFileSync(join(base, ".gsd", "milestones", "M001.md"), "# M001\n", "utf-8");
|
|
102
|
+
mkdirSync(join(base, ".gsd 2"), { recursive: true });
|
|
103
|
+
mkdirSync(join(base, ".gsd 3"), { recursive: true });
|
|
104
|
+
|
|
105
|
+
const result = ensureGsdSymlink(base);
|
|
106
|
+
// When .gsd is a real directory, ensureGsdSymlink preserves it
|
|
107
|
+
assertEq(result, join(base, ".gsd"), "real .gsd directory preserved");
|
|
108
|
+
assertTrue(lstatSync(join(base, ".gsd")).isDirectory(), ".gsd remains a directory");
|
|
109
|
+
|
|
110
|
+
// But the numbered variants should still be cleaned up
|
|
111
|
+
assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" cleaned even when .gsd is a directory');
|
|
112
|
+
assertTrue(!existsSync(join(base, ".gsd 3")), '".gsd 3" cleaned even when .gsd is a directory');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Test: only numeric-suffixed variants are removed ───────────────
|
|
116
|
+
console.log("\n=== ensureGsdSymlink only removes .gsd + space + digit variants ===");
|
|
117
|
+
{
|
|
118
|
+
rmSync(join(base, ".gsd"), { recursive: true, force: true });
|
|
119
|
+
|
|
120
|
+
// These should NOT be touched
|
|
121
|
+
mkdirSync(join(base, ".gsd-backup"), { recursive: true });
|
|
122
|
+
mkdirSync(join(base, ".gsd_old"), { recursive: true });
|
|
123
|
+
|
|
124
|
+
// These SHOULD be removed (macOS collision pattern)
|
|
125
|
+
mkdirSync(join(base, ".gsd 2"), { recursive: true });
|
|
126
|
+
mkdirSync(join(base, ".gsd 10"), { recursive: true });
|
|
127
|
+
|
|
128
|
+
ensureGsdSymlink(base);
|
|
129
|
+
|
|
130
|
+
assertTrue(existsSync(join(base, ".gsd-backup")), ".gsd-backup is NOT removed");
|
|
131
|
+
assertTrue(existsSync(join(base, ".gsd_old")), ".gsd_old is NOT removed");
|
|
132
|
+
assertTrue(!existsSync(join(base, ".gsd 2")), '".gsd 2" removed');
|
|
133
|
+
assertTrue(!existsSync(join(base, ".gsd 10")), '".gsd 10" removed');
|
|
134
|
+
|
|
135
|
+
// Cleanup non-variant dirs
|
|
136
|
+
rmSync(join(base, ".gsd-backup"), { recursive: true, force: true });
|
|
137
|
+
rmSync(join(base, ".gsd_old"), { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} finally {
|
|
141
|
+
delete process.env.GSD_STATE_DIR;
|
|
142
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
143
|
+
try { rmSync(stateDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
144
|
+
report();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
main().catch((error) => {
|
|
149
|
+
console.error(error);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|