@tekyzinc/gsd-t 3.26.11 → 3.27.10

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.
@@ -384,3 +384,88 @@ test("12. clock injection — timestamp uses injected clock", async () => {
384
384
  const state = JSON.parse(fs.readFileSync(stateFile(tmpRoot), "utf8"));
385
385
  assert.equal(state.timestamp, fixed.toISOString());
386
386
  });
387
+
388
+ /* ── M-fix: model-aware context window (the reported regression) ───────── */
389
+
390
+ test("13. Opus 4.7 @ ~36% of a 1M window stays 'normal' (regression repro)", async () => {
391
+ // The exact reported symptom: ~360k tokens used on an Opus 4.7 session.
392
+ // With the old hardcoded 200k window this computed 180% → premature
393
+ // headless handoff at ~64% of context REMAINING. With model-aware sizing
394
+ // the window is 1M, so 360k = 36% = normal, no handoff.
395
+ seedState(tmpRoot, { checkCount: 4 });
396
+
397
+ const out = await runMeter({
398
+ payload: makePayload(),
399
+ projectRoot: tmpRoot,
400
+ _loadConfig: () => makeConfig(), // config still says 200k — must be overridden
401
+ _parseTranscript: async () => ({ ...FAKE_PARSED, model: "claude-opus-4-7" }),
402
+ _estimateTokens: () => ({ inputTokens: 360000 }),
403
+ });
404
+
405
+ // No handoff marker — this is the whole point of the fix.
406
+ assert.deepEqual(out, {});
407
+
408
+ const state = JSON.parse(fs.readFileSync(stateFile(tmpRoot), "utf8"));
409
+ assert.equal(state.modelWindowSize, 1_000_000, "window resolved from model, not config");
410
+ assert.equal(state.pct, 36, "360k / 1M = 36%");
411
+ assert.equal(state.threshold, "normal");
412
+ });
413
+
414
+ test("14. Opus 4.7 @ 80% of the true 1M window DOES hand off", async () => {
415
+ // The handoff must still fire at the real 75% threshold against the
416
+ // corrected window — we keep the guard, we just size it correctly.
417
+ seedState(tmpRoot, { checkCount: 4 });
418
+
419
+ const out = await runMeter({
420
+ payload: makePayload(),
421
+ projectRoot: tmpRoot,
422
+ _loadConfig: () => makeConfig(),
423
+ _parseTranscript: async () => ({ ...FAKE_PARSED, model: "claude-opus-4-7-20260115" }),
424
+ _estimateTokens: () => ({ inputTokens: 800000 }), // 80% of 1M > 75%
425
+ });
426
+
427
+ assert.equal(out.additionalContext, "next-spawn-headless:true");
428
+ const state = JSON.parse(fs.readFileSync(stateFile(tmpRoot), "utf8"));
429
+ assert.equal(state.modelWindowSize, 1_000_000);
430
+ assert.equal(state.pct, 80);
431
+ assert.equal(state.threshold, "threshold");
432
+ });
433
+
434
+ test("15. no model in transcript → falls back to config window (back-compat)", async () => {
435
+ // Existing transcripts / stubs without a model field must behave exactly
436
+ // as before: config's modelWindowSize governs.
437
+ seedState(tmpRoot, { checkCount: 4 });
438
+
439
+ const out = await runMeter({
440
+ payload: makePayload(),
441
+ projectRoot: tmpRoot,
442
+ _loadConfig: () => makeConfig({ modelWindowSize: 200000 }),
443
+ _parseTranscript: async () => FAKE_PARSED, // no `model` key
444
+ _estimateTokens: () => ({ inputTokens: 160000 }), // 80% of 200k
445
+ });
446
+
447
+ assert.equal(out.additionalContext, "next-spawn-headless:true");
448
+ const state = JSON.parse(fs.readFileSync(stateFile(tmpRoot), "utf8"));
449
+ assert.equal(state.modelWindowSize, 200000);
450
+ assert.equal(state.pct, 80);
451
+ });
452
+
453
+ test("16. Haiku session correctly sized at 200k (not over-large 1M)", async () => {
454
+ seedState(tmpRoot, { checkCount: 4 });
455
+
456
+ const out = await runMeter({
457
+ payload: makePayload(),
458
+ projectRoot: tmpRoot,
459
+ _loadConfig: () => makeConfig(),
460
+ _parseTranscript: async () => ({
461
+ ...FAKE_PARSED,
462
+ model: "claude-haiku-4-5-20251001",
463
+ }),
464
+ _estimateTokens: () => ({ inputTokens: 170000 }), // 85% of 200k
465
+ });
466
+
467
+ assert.equal(out.additionalContext, "next-spawn-headless:true");
468
+ const state = JSON.parse(fs.readFileSync(stateFile(tmpRoot), "utf8"));
469
+ assert.equal(state.modelWindowSize, 200000);
470
+ assert.equal(state.pct, 85);
471
+ });
@@ -537,6 +537,12 @@ BEFORE EVERY COMMIT:
537
537
  │ YES → Verify test names and paths are referenced in requirements
538
538
  ├── Did I change UI, routes, or user flows?
539
539
  │ YES → Update affected E2E test specs (Playwright/Cypress)
540
+ ├── Did I add a new top-level dir, or change build/CI config?
541
+ │ This is ENFORCED MECHANICALLY by `gsd-t-verify` Step 2.6
542
+ │ (CI-Parity Gate: `gsd-t build-coverage` + `gsd-t ci-parity`,
543
+ │ FAIL-blocking). You do NOT self-attest this — verify runs the
544
+ │ real CI build. It exists because TimeTracking v1.10.12 shipped
545
+ │ VERIFIED+tagged with a new dir absent from the Dockerfile COPY.
540
546
  └── Did I run the affected tests?
541
547
  YES → Verify they pass. NO → Run them now.
542
548
  ```