gsd-pi 2.44.0-dev.848dd4c → 2.44.0-dev.8894d5b
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/dist/resources/extensions/gsd/bootstrap/db-tools.js +20 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
- package/dist/resources/extensions/gsd/preferences.js +9 -1
- package/dist/resources/extensions/gsd/state.js +19 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- 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/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 +19 -19
- 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/package.json +1 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +19 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
- package/src/resources/extensions/gsd/preferences.ts +11 -1
- package/src/resources/extensions/gsd/state.ts +19 -1
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
- /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → oZMtyM-zfu6Inx-S59cOl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → oZMtyM-zfu6Inx-S59cOl}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
insertArtifact,
|
|
12
12
|
isDbAvailable,
|
|
13
13
|
insertMilestone,
|
|
14
|
+
getAllMilestones,
|
|
14
15
|
insertSlice,
|
|
15
16
|
insertTask,
|
|
16
17
|
} from '../gsd-db.ts';
|
|
@@ -962,4 +963,63 @@ describe('derive-state-db', async () => {
|
|
|
962
963
|
cleanup(base);
|
|
963
964
|
}
|
|
964
965
|
});
|
|
966
|
+
|
|
967
|
+
// ─── Regression: disk-only milestones synced into DB (#2416) ─────────
|
|
968
|
+
test('derive-state-db: disk-only milestone auto-synced into DB (#2416)', async () => {
|
|
969
|
+
const base = createFixtureBase();
|
|
970
|
+
try {
|
|
971
|
+
// M001 is complete and exists in DB. M002 was queued on disk only — no DB row.
|
|
972
|
+
writeFile(base, 'milestones/M001/M001-SUMMARY.md', '# M001 Summary\n\nDone.');
|
|
973
|
+
writeFile(base, 'milestones/M002/M002-CONTEXT.md', '# M002: Queued\n\nQueued milestone.');
|
|
974
|
+
|
|
975
|
+
openDatabase(':memory:');
|
|
976
|
+
// Only insert M001 — simulates the state after migration guard ran then /gsd queue added M002
|
|
977
|
+
insertMilestone({ id: 'M001', title: 'First', status: 'complete' });
|
|
978
|
+
|
|
979
|
+
invalidateStateCache();
|
|
980
|
+
const state = await deriveStateFromDb(base);
|
|
981
|
+
|
|
982
|
+
// Before the fix, M002 was invisible: getAllMilestones() returned only M001
|
|
983
|
+
// (complete) → phase='complete' → auto-mode stopped.
|
|
984
|
+
// After the fix, deriveStateFromDb reconciles disk dirs and inserts M002.
|
|
985
|
+
assert.deepStrictEqual(state.phase, 'pre-planning', 'disk-sync-2416: phase is pre-planning, not complete');
|
|
986
|
+
assert.deepStrictEqual(state.registry.length, 2, 'disk-sync-2416: both milestones visible in registry');
|
|
987
|
+
assert.deepStrictEqual(state.registry[0]?.id, 'M001', 'disk-sync-2416: registry[0] is M001');
|
|
988
|
+
assert.deepStrictEqual(state.registry[0]?.status, 'complete', 'disk-sync-2416: M001 is complete');
|
|
989
|
+
assert.deepStrictEqual(state.registry[1]?.id, 'M002', 'disk-sync-2416: registry[1] is M002');
|
|
990
|
+
assert.deepStrictEqual(state.registry[1]?.status, 'active', 'disk-sync-2416: M002 is active');
|
|
991
|
+
assert.deepStrictEqual(state.activeMilestone?.id, 'M002', 'disk-sync-2416: activeMilestone is M002');
|
|
992
|
+
|
|
993
|
+
closeDatabase();
|
|
994
|
+
} finally {
|
|
995
|
+
closeDatabase();
|
|
996
|
+
cleanup(base);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// ─── Queued milestone row not clobbered by later plan (#2416 root cause) ──
|
|
1001
|
+
test('derive-state-db: queued milestone row survives gsd_plan_milestone INSERT OR IGNORE', async () => {
|
|
1002
|
+
try {
|
|
1003
|
+
openDatabase(':memory:');
|
|
1004
|
+
|
|
1005
|
+
// Simulates gsd_milestone_generate_id inserting a minimal queued row
|
|
1006
|
+
insertMilestone({ id: 'M001', status: 'queued' });
|
|
1007
|
+
|
|
1008
|
+
const before = getAllMilestones();
|
|
1009
|
+
assert.equal(before.length, 1, 'queued-row: one row after generate_id');
|
|
1010
|
+
assert.equal(before[0]!.status, 'queued', 'queued-row: status is queued');
|
|
1011
|
+
|
|
1012
|
+
// Simulates gsd_plan_milestone calling insertMilestone (INSERT OR IGNORE)
|
|
1013
|
+
insertMilestone({ id: 'M001', title: 'Planned Title', status: 'active' });
|
|
1014
|
+
|
|
1015
|
+
const after = getAllMilestones();
|
|
1016
|
+
assert.equal(after.length, 1, 'queued-row: still one row after plan');
|
|
1017
|
+
// INSERT OR IGNORE keeps the original row — status stays 'queued'
|
|
1018
|
+
assert.equal(after[0]!.status, 'queued', 'queued-row: INSERT OR IGNORE preserves original status');
|
|
1019
|
+
|
|
1020
|
+
closeDatabase();
|
|
1021
|
+
} finally {
|
|
1022
|
+
closeDatabase();
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
965
1025
|
});
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - resolveGsdRootFile resolves KNOWLEDGE paths correctly
|
|
7
7
|
* - inlineGsdRootFile works with the KNOWLEDGE key
|
|
8
8
|
* - before_agent_start hook includes/omits knowledge block appropriately
|
|
9
|
+
* - loadKnowledgeBlock merges global and project knowledge correctly
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import test from 'node:test';
|
|
@@ -16,6 +17,7 @@ import { tmpdir } from 'node:os';
|
|
|
16
17
|
import { GSD_ROOT_FILES, resolveGsdRootFile } from '../paths.ts';
|
|
17
18
|
import { inlineGsdRootFile } from '../auto-prompts.ts';
|
|
18
19
|
import { appendKnowledge } from '../files.ts';
|
|
20
|
+
import { loadKnowledgeBlock } from '../bootstrap/system-context.ts';
|
|
19
21
|
|
|
20
22
|
// ─── KNOWLEDGE is registered in GSD_ROOT_FILES ─────────────────────────────
|
|
21
23
|
|
|
@@ -159,3 +161,90 @@ test('knowledge: appendKnowledge handles lesson type', async () => {
|
|
|
159
161
|
|
|
160
162
|
rmSync(tmp, { recursive: true, force: true });
|
|
161
163
|
});
|
|
164
|
+
|
|
165
|
+
// ─── loadKnowledgeBlock — global + project merge ────────────────────────────
|
|
166
|
+
|
|
167
|
+
test('loadKnowledgeBlock: returns empty block when neither file exists', () => {
|
|
168
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-kb-')));
|
|
169
|
+
const gsdHome = join(tmp, 'home');
|
|
170
|
+
const cwd = join(tmp, 'project');
|
|
171
|
+
mkdirSync(join(cwd, '.gsd'), { recursive: true });
|
|
172
|
+
mkdirSync(join(gsdHome, 'agent'), { recursive: true });
|
|
173
|
+
|
|
174
|
+
const result = loadKnowledgeBlock(gsdHome, cwd);
|
|
175
|
+
assert.strictEqual(result.block, '');
|
|
176
|
+
assert.strictEqual(result.globalSizeKb, 0);
|
|
177
|
+
|
|
178
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('loadKnowledgeBlock: uses project knowledge alone when no global file', () => {
|
|
182
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-kb-')));
|
|
183
|
+
const gsdHome = join(tmp, 'home');
|
|
184
|
+
const cwd = join(tmp, 'project');
|
|
185
|
+
mkdirSync(join(cwd, '.gsd'), { recursive: true });
|
|
186
|
+
mkdirSync(join(gsdHome, 'agent'), { recursive: true });
|
|
187
|
+
writeFileSync(join(cwd, '.gsd', 'KNOWLEDGE.md'), 'K001: Use real DB');
|
|
188
|
+
|
|
189
|
+
const result = loadKnowledgeBlock(gsdHome, cwd);
|
|
190
|
+
assert.ok(result.block.includes('[KNOWLEDGE — Rules, patterns, and lessons learned]'));
|
|
191
|
+
assert.ok(result.block.includes('## Project Knowledge'));
|
|
192
|
+
assert.ok(result.block.includes('K001: Use real DB'));
|
|
193
|
+
assert.ok(!result.block.includes('## Global Knowledge'));
|
|
194
|
+
assert.strictEqual(result.globalSizeKb, 0);
|
|
195
|
+
|
|
196
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('loadKnowledgeBlock: uses global knowledge alone when no project file', () => {
|
|
200
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-kb-')));
|
|
201
|
+
const gsdHome = join(tmp, 'home');
|
|
202
|
+
const cwd = join(tmp, 'project');
|
|
203
|
+
mkdirSync(join(cwd, '.gsd'), { recursive: true });
|
|
204
|
+
mkdirSync(join(gsdHome, 'agent'), { recursive: true });
|
|
205
|
+
writeFileSync(join(gsdHome, 'agent', 'KNOWLEDGE.md'), 'G001: Respond in English');
|
|
206
|
+
|
|
207
|
+
const result = loadKnowledgeBlock(gsdHome, cwd);
|
|
208
|
+
assert.ok(result.block.includes('[KNOWLEDGE — Rules, patterns, and lessons learned]'));
|
|
209
|
+
assert.ok(result.block.includes('## Global Knowledge'));
|
|
210
|
+
assert.ok(result.block.includes('G001: Respond in English'));
|
|
211
|
+
assert.ok(!result.block.includes('## Project Knowledge'));
|
|
212
|
+
assert.ok(result.globalSizeKb > 0);
|
|
213
|
+
|
|
214
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('loadKnowledgeBlock: merges global before project when both exist', () => {
|
|
218
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-kb-')));
|
|
219
|
+
const gsdHome = join(tmp, 'home');
|
|
220
|
+
const cwd = join(tmp, 'project');
|
|
221
|
+
mkdirSync(join(cwd, '.gsd'), { recursive: true });
|
|
222
|
+
mkdirSync(join(gsdHome, 'agent'), { recursive: true });
|
|
223
|
+
writeFileSync(join(gsdHome, 'agent', 'KNOWLEDGE.md'), 'G001: Global rule');
|
|
224
|
+
writeFileSync(join(cwd, '.gsd', 'KNOWLEDGE.md'), 'K001: Project rule');
|
|
225
|
+
|
|
226
|
+
const result = loadKnowledgeBlock(gsdHome, cwd);
|
|
227
|
+
assert.ok(result.block.includes('## Global Knowledge'));
|
|
228
|
+
assert.ok(result.block.includes('## Project Knowledge'));
|
|
229
|
+
assert.ok(result.block.includes('G001: Global rule'));
|
|
230
|
+
assert.ok(result.block.includes('K001: Project rule'));
|
|
231
|
+
// Global section appears before project section
|
|
232
|
+
assert.ok(result.block.indexOf('## Global Knowledge') < result.block.indexOf('## Project Knowledge'));
|
|
233
|
+
|
|
234
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('loadKnowledgeBlock: reports globalSizeKb above 4KB threshold', () => {
|
|
238
|
+
const tmp = realpathSync(mkdtempSync(join(tmpdir(), 'gsd-kb-')));
|
|
239
|
+
const gsdHome = join(tmp, 'home');
|
|
240
|
+
const cwd = join(tmp, 'project');
|
|
241
|
+
mkdirSync(join(cwd, '.gsd'), { recursive: true });
|
|
242
|
+
mkdirSync(join(gsdHome, 'agent'), { recursive: true });
|
|
243
|
+
// Write > 4KB of content
|
|
244
|
+
writeFileSync(join(gsdHome, 'agent', 'KNOWLEDGE.md'), 'x'.repeat(5000));
|
|
245
|
+
|
|
246
|
+
const result = loadKnowledgeBlock(gsdHome, cwd);
|
|
247
|
+
assert.ok(result.globalSizeKb > 4, `expected > 4KB, got ${result.globalSizeKb}`);
|
|
248
|
+
|
|
249
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
250
|
+
});
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
applyModeDefaults,
|
|
16
16
|
getIsolationMode,
|
|
17
17
|
parsePreferencesMarkdown,
|
|
18
|
+
_resetParseWarningFlag,
|
|
18
19
|
} from "../preferences.ts";
|
|
19
20
|
import type { GSDPreferences, GSDModelConfigV2, GSDPhaseModelConfig } from "../preferences.ts";
|
|
20
21
|
|
|
@@ -352,3 +353,29 @@ test("handles empty models config", () => {
|
|
|
352
353
|
assert.notEqual(prefs, null);
|
|
353
354
|
assert.equal(prefs!.models, undefined);
|
|
354
355
|
});
|
|
356
|
+
|
|
357
|
+
// ── Warn-once for unrecognized format (#2373) ────────────────────────────────
|
|
358
|
+
|
|
359
|
+
test("unrecognized format warning is emitted at most once (#2373)", () => {
|
|
360
|
+
const warnings: string[] = [];
|
|
361
|
+
const origWarn = console.warn;
|
|
362
|
+
console.warn = (...args: unknown[]) => warnings.push(args.join(" "));
|
|
363
|
+
try {
|
|
364
|
+
// Reset internal warned flag so the test starts clean
|
|
365
|
+
_resetParseWarningFlag();
|
|
366
|
+
|
|
367
|
+
const unrecognized = "This is just plain text with no frontmatter or headings.";
|
|
368
|
+
|
|
369
|
+
// Call multiple times — simulates repeated preference loads
|
|
370
|
+
parsePreferencesMarkdown(unrecognized);
|
|
371
|
+
parsePreferencesMarkdown(unrecognized);
|
|
372
|
+
parsePreferencesMarkdown(unrecognized);
|
|
373
|
+
|
|
374
|
+
const relevant = warnings.filter(w => w.includes("unrecognized format"));
|
|
375
|
+
assert.equal(relevant.length, 1, `expected exactly 1 warning, got ${relevant.length}: ${JSON.stringify(relevant)}`);
|
|
376
|
+
} finally {
|
|
377
|
+
console.warn = origWarn;
|
|
378
|
+
// Reset so other tests aren't affected by the flag state
|
|
379
|
+
_resetParseWarningFlag();
|
|
380
|
+
}
|
|
381
|
+
});
|
|
File without changes
|
|
File without changes
|