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.
Files changed (45) hide show
  1. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +20 -0
  2. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
  3. package/dist/resources/extensions/gsd/preferences.js +9 -1
  4. package/dist/resources/extensions/gsd/state.js +19 -2
  5. package/dist/web/standalone/.next/BUILD_ID +1 -1
  6. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  7. package/dist/web/standalone/.next/build-manifest.json +2 -2
  8. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  9. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  10. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  18. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/index.html +1 -1
  26. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  33. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  34. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  35. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  36. package/package.json +1 -1
  37. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +19 -0
  38. package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
  39. package/src/resources/extensions/gsd/preferences.ts +11 -1
  40. package/src/resources/extensions/gsd/state.ts +19 -1
  41. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
  42. package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
  43. package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
  44. /package/dist/web/standalone/.next/static/{-zps1Q9mQmioAKLcQiCr8 → oZMtyM-zfu6Inx-S59cOl}/_buildManifest.js +0 -0
  45. /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
+ });