skillrepo 4.9.1 → 4.9.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillrepo",
3
- "version": "4.9.1",
3
+ "version": "4.9.2",
4
4
  "description": "Pull-based CLI for agent skills — init, sync, search, add, remove your library from any IDE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -551,6 +551,66 @@ describe("getLibrary", () => {
551
551
  }
552
552
  });
553
553
 
554
+ // ── Wire identity (#2358 — skillset epic no-file contract) ──────────
555
+ //
556
+ // The skillset epic's D2 contract: a repo with no skillset declaration
557
+ // must produce request/receipt payloads byte-identical to the
558
+ // pre-skillset CLI, forever. These tests capture today's wire shape so
559
+ // any future change to it — a new query param, a pin/skillset header —
560
+ // fails here first and has to be made deliberately.
561
+
562
+ it("wire identity: a plain sync sends the exact pre-skillset request shape (#2358)", async () => {
563
+ let captured;
564
+ const srv = await startServer((req, res) => {
565
+ captured = { url: req.url, method: req.method, headers: { ...req.headers } };
566
+ jsonRes(res, 200, { skills: [], removals: [], syncedAt: "x" });
567
+ });
568
+ try {
569
+ await getLibrary(srv.url, VALID_KEY);
570
+
571
+ // Exact path — no query string at all on a plain sync.
572
+ assert.equal(captured.url, "/api/v1/library");
573
+ assert.equal(captured.method, "GET");
574
+ // Auth rides the Authorization header, and no pin/skillset-shaped
575
+ // vocabulary appears in any header name or value.
576
+ assert.match(captured.headers.authorization, /^Bearer /);
577
+ const headerBlob = JSON.stringify(captured.headers);
578
+ assert.doesNotMatch(headerBlob, /skillset|approved|pin/i);
579
+ } finally {
580
+ await srv.close();
581
+ }
582
+ });
583
+
584
+ it("wire identity: a pin-suffixed ETag round-trips verbatim as an opaque string (#2358)", async () => {
585
+ // Servers emit `"{ts}-{count}-{removals}-p{ts}-{n}"` for accounts
586
+ // with pin history. The CLI must treat every ETag as opaque: store
587
+ // it exactly, send it back exactly. A client that parsed the legacy
588
+ // three-segment shape would corrupt the conditional-GET cycle for
589
+ // pinned accounts.
590
+ const SUFFIXED = '"1754680000000-12-3-p1754690000000-2"';
591
+ let conditional;
592
+ const srv = await startServer((req, res) => {
593
+ if (req.headers["if-none-match"]) {
594
+ conditional = req.headers["if-none-match"];
595
+ res.statusCode = 304;
596
+ res.end();
597
+ return;
598
+ }
599
+ jsonRes(res, 200, { skills: [], removals: [], syncedAt: "x" }, { ETag: SUFFIXED });
600
+ });
601
+ try {
602
+ const first = await getLibrary(srv.url, VALID_KEY);
603
+ assert.equal(first.etag, SUFFIXED);
604
+
605
+ const second = await getLibrary(srv.url, VALID_KEY, { ifNoneMatch: first.etag });
606
+ assert.equal(conditional, SUFFIXED);
607
+ assert.equal(second.notModified, true);
608
+ assert.equal(second.etag, SUFFIXED);
609
+ } finally {
610
+ await srv.close();
611
+ }
612
+ });
613
+
554
614
  it("throws authError on 401", async () => {
555
615
  const srv = await startServer((req, res) => jsonRes(res, 401, { error: "nope" }));
556
616
  try {