@appsforgood/next-supabase-kit 0.1.7 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.8
4
+
5
+ - Added `tests/setup-server-api.test.ts` for full setup-server API route coverage (PATCH state, context import, checklists, design/messaging drafts, apply, agentic-level refresh, oversized body, 404) plus wizard draft edge cases.
6
+ - Moved `readJsonBody` to `src/studio/shared.ts` for reuse; setup-server validates invalid `completeSection` ids with 400.
7
+ - Added Playwright UI screenshot smoke (`scripts/smoke-ui-screens.mjs`, `npm run smoke:ui-screens`, Playwright 1.55.1) with dedicated Ubuntu CI job uploading `artifacts/ui-screens/` (not in `release:check`).
8
+ - Completed Agent Studio Milestone 8: `POST /api/sessions/:id/note` and `/render` on studio serve, session picker, note form, and render button in live office UI; SSE broadcast for notes.
9
+ - Updated `TESTING.md`, `QUALITY_GATES.md`, `DOCS.md`, `ROADMAP.md`, and `DECISIONS.md` for studio serve endpoints, UI smoke, and runtime adapter references.
10
+
3
11
  ## 0.1.7
4
12
 
5
13
  - Added **Workflow Commands** section to README and docs site: lifecycle diagram, 12 core + 8 UI slash commands, council table, skills-by-phase grouping, and explicit separation from package CLI commands.
package/DOGFOOD.md CHANGED
@@ -34,6 +34,16 @@ Mode: read-only audit; no downstream files were modified.
34
34
  - Assistant adapters and upgrade lifecycle still need real activation/dogfood evidence after publication.
35
35
  - Reference-led design critique still needs a real UI change dogfood pass with screenshots or equivalent visual evidence.
36
36
 
37
+ ## 2026-07-04 Publish @0.1.8 Snapshot
38
+
39
+ Date: 2026-07-04
40
+ CLI source: local `dist/index.js` after `npm run release:check`
41
+
42
+ - Setup-server API route coverage (`tests/setup-server-api.test.ts`), Playwright UI screenshot smoke (`smoke:ui-screens`, Playwright 1.55.1), and Agent Studio Milestone 8 completion (note/render POST endpoints, session picker, live office controls).
43
+ - `readJsonBody` shared between setup and studio servers; oversized bodies return 400 without dropping the connection; studio SSE broadcasts notes.
44
+ - Dedicated Ubuntu CI job for UI screenshots; not part of `release:check` matrix.
45
+ - Local verification: `release:check` green (150 tests, setup-server.ts 86% statements), `smoke:ui-screens` produced 4 PNGs.
46
+
37
47
  ## 2026-07-04 Publish @0.1.7 Snapshot
38
48
 
39
49
  Date: 2026-07-04
package/README.md CHANGED
@@ -20,10 +20,10 @@ It also includes a local Agent Studio workflow: project context, durable human c
20
20
 
21
21
  ## Quick Start
22
22
 
23
- Use this in a Next.js + Supabase project (latest: **v0.1.7** on npm):
23
+ Use this in a Next.js + Supabase project (latest: **v0.1.8** on npm):
24
24
 
25
25
  ```bash
26
- npx @appsforgood/next-supabase-kit@0.1.7 init --stack next-supabase --setup --open
26
+ npx @appsforgood/next-supabase-kit@0.1.8 init --stack next-supabase --setup --open
27
27
  npx @appsforgood/next-supabase-kit audit
28
28
  npx @appsforgood/next-supabase-kit audit --min-readiness baseline-setup
29
29
  ```
@@ -458,7 +458,7 @@ Release expectations:
458
458
  - Dependency Review, CodeQL, OpenSSF Scorecard, Dependabot, SBOM validation, and SBOM attestation.
459
459
  - Post-publish verification with `npm run publish:verify`.
460
460
 
461
- The package is published to public npm under `@appsforgood/next-supabase-kit@0.1.7`. Every release must pass `npm run release:check` before publish and `npm run publish:verify` after (registry visibility, clean `npx` doctor/init/audit). Post-publish verification last passed **2026-07-04** against the live registry: `@0.1.7` doctor, clean init, and `audit --min-readiness baseline-setup` with zero failures.
461
+ The package is published to public npm under `@appsforgood/next-supabase-kit@0.1.8`. Every release must pass `npm run release:check` before publish and `npm run publish:verify` after (registry visibility, clean `npx` doctor/init/audit). Post-publish verification last passed **2026-07-04** against the live registry: `@0.1.7` doctor, clean init, and `audit --min-readiness baseline-setup` with zero failures.
462
462
 
463
463
  ## Repository Health
464
464
 
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "name": "agent-kit-next-supabase",
4
4
  "displayName": "Agent Kit Next/Supabase",
5
- "version": "0.1.7",
5
+ "version": "0.1.8",
6
6
  "description": "Runtime commands and portable skills for the Agent Kit Next/Supabase council workflow.",
7
7
  "homepage": "https://github.com/lukey662/agentsandskills",
8
8
  "sourceOfTruth": [
package/dist/index.js CHANGED
@@ -150,7 +150,7 @@ import { join as join12, normalize } from "path";
150
150
 
151
151
  // src/config/defaults.ts
152
152
  var PACKAGE_NAME = "@appsforgood/next-supabase-kit";
153
- var PACKAGE_VERSION = "0.1.7";
153
+ var PACKAGE_VERSION = "0.1.8";
154
154
  var DEFAULT_CONFIG = {
155
155
  stack: "next-supabase",
156
156
  projectType: "saas",
@@ -362,6 +362,34 @@ function listMarkdown(items) {
362
362
  function unique(values) {
363
363
  return [...new Set(values.filter(Boolean))].sort();
364
364
  }
365
+ function readJsonBody(request) {
366
+ return new Promise((resolve3, reject) => {
367
+ const chunks = [];
368
+ let bodyTooLarge = false;
369
+ request.on("data", (chunk) => {
370
+ if (bodyTooLarge) return;
371
+ chunks.push(chunk);
372
+ if (chunks.reduce((total, item) => total + item.length, 0) > 256e3) {
373
+ bodyTooLarge = true;
374
+ reject(new Error("Request body too large."));
375
+ }
376
+ });
377
+ request.on("end", () => {
378
+ if (bodyTooLarge) return;
379
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
380
+ if (!raw) {
381
+ resolve3({});
382
+ return;
383
+ }
384
+ try {
385
+ resolve3(JSON.parse(raw));
386
+ } catch {
387
+ reject(new Error("Request body must be valid JSON."));
388
+ }
389
+ });
390
+ request.on("error", reject);
391
+ });
392
+ }
365
393
 
366
394
  // src/install/audit.ts
367
395
  import { existsSync as existsSync11, readFileSync as readFileSync9, statSync } from "fs";
@@ -4548,7 +4576,10 @@ function redactEvent(event) {
4548
4576
  };
4549
4577
  }
4550
4578
  function recordNote(cwd, agentId, text) {
4551
- return appendSessionEvent(cwd, getActiveSessionId(cwd), { type: "agent_message", createdAt: nowIso(), agentId, text });
4579
+ return recordSessionNote(cwd, getActiveSessionId(cwd), agentId, text);
4580
+ }
4581
+ function recordSessionNote(cwd, sessionId, agentId, text) {
4582
+ return appendSessionEvent(cwd, sessionId, { type: "agent_message", createdAt: nowIso(), agentId, text });
4552
4583
  }
4553
4584
  function recordDecision(cwd, agentId, decision, risk) {
4554
4585
  return appendSessionEvent(cwd, getActiveSessionId(cwd), {
@@ -5709,7 +5740,7 @@ function renderOfficeHtml(boot, mode) {
5709
5740
  <div id="nameplate-layer" class="nameplate-layer" aria-hidden="true"></div>
5710
5741
  <div id="office-hint" class="office-hint hidden" role="status">${isStudio ? "Watching council session events\u2026" : "Click a desk or zone to brief your agent team."}</div>
5711
5742
  </div>
5712
- ${isStudio ? '<aside class="transcript-panel" id="transcript-panel" aria-label="Session transcript"><h2>Transcript</h2><ol id="transcript-list"></ol></aside>' : ""}
5743
+ ${isStudio ? '<aside class="transcript-panel" id="transcript-panel" aria-label="Session transcript"><div class="studio-controls" id="studio-controls"><label class="studio-label" for="session-picker">Session</label><select id="session-picker" aria-label="Council session"></select><form id="studio-note-form" class="studio-note-form"><select id="studio-note-agent" aria-label="Agent for note"></select><input id="studio-note-text" type="text" maxlength="3999" placeholder="Add council note\u2026" /><button type="submit" class="btn secondary">Add note</button></form><button type="button" class="btn primary" id="studio-render-btn">Render markdown</button></div><h2>Transcript</h2><ol id="transcript-list"></ol></aside>' : ""}
5713
5744
  </main>
5714
5745
  <div id="status" class="status" role="status" aria-live="polite"></div>
5715
5746
  <div id="depth-modal" class="modal modal-blur" hidden>
@@ -6164,31 +6195,6 @@ function invalidateAgenticLevelCache(cwd) {
6164
6195
  // src/studio/setup-server.ts
6165
6196
  var DEFAULT_PORT = 9321;
6166
6197
  var DEFAULT_HOST = "127.0.0.1";
6167
- function readJsonBody(request) {
6168
- return new Promise((resolve3, reject) => {
6169
- const chunks = [];
6170
- request.on("data", (chunk) => {
6171
- chunks.push(chunk);
6172
- if (chunks.reduce((total, item) => total + item.length, 0) > 256e3) {
6173
- reject(new Error("Request body too large."));
6174
- request.destroy();
6175
- }
6176
- });
6177
- request.on("end", () => {
6178
- const raw = Buffer.concat(chunks).toString("utf8").trim();
6179
- if (!raw) {
6180
- resolve3({});
6181
- return;
6182
- }
6183
- try {
6184
- resolve3(JSON.parse(raw));
6185
- } catch {
6186
- reject(new Error("Request body must be valid JSON."));
6187
- }
6188
- });
6189
- request.on("error", reject);
6190
- });
6191
- }
6192
6198
  function sendJson(response, statusCode, payload) {
6193
6199
  response.writeHead(statusCode, {
6194
6200
  "Content-Type": "application/json; charset=utf-8",
@@ -6276,6 +6282,9 @@ async function handleRequest(cwd, request, response) {
6276
6282
  try {
6277
6283
  const body = await readJsonBody(request);
6278
6284
  if (typeof body.completeSection === "string") {
6285
+ if (!(body.completeSection in SECTION_LABELS)) {
6286
+ throw new Error("Invalid section id.");
6287
+ }
6279
6288
  markSectionComplete(cwd, body.completeSection);
6280
6289
  }
6281
6290
  const patch = {};
@@ -6544,6 +6553,11 @@ function safeSessionId(raw) {
6544
6553
  return raw;
6545
6554
  }
6546
6555
  function handleRequest2(cwd, request, response) {
6556
+ handleRequestAsync(cwd, request, response).catch((error) => {
6557
+ sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
6558
+ });
6559
+ }
6560
+ async function handleRequestAsync(cwd, request, response) {
6547
6561
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
6548
6562
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office")) {
6549
6563
  sendHtml2(response, renderLiveStudioHtmlWithContext(cwd));
@@ -6618,6 +6632,57 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
6618
6632
  });
6619
6633
  return;
6620
6634
  }
6635
+ const noteMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/note$/);
6636
+ if (request.method === "POST" && noteMatch) {
6637
+ const sessionId = safeSessionId(noteMatch[1] ?? "");
6638
+ if (!sessionId) {
6639
+ sendJson2(response, 400, { error: "Invalid session id." });
6640
+ return;
6641
+ }
6642
+ try {
6643
+ const body = await readJsonBody(request);
6644
+ const agent = typeof body.agent === "string" ? body.agent.trim() : "";
6645
+ const text = typeof body.text === "string" ? body.text.trim() : "";
6646
+ if (!agent) {
6647
+ sendJson2(response, 400, { error: "agent is required." });
6648
+ return;
6649
+ }
6650
+ if (!text) {
6651
+ sendJson2(response, 400, { error: "text is required." });
6652
+ return;
6653
+ }
6654
+ if (text.length >= 4e3) {
6655
+ sendJson2(response, 400, { error: "text must be under 4000 characters." });
6656
+ return;
6657
+ }
6658
+ const event = recordSessionNote(cwd, sessionId, agent, text);
6659
+ broadcastSse("event", { sessionId, event, total: readSessionEvents(cwd, sessionId).length });
6660
+ sendJson2(response, 200, { event });
6661
+ } catch (error) {
6662
+ sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
6663
+ }
6664
+ return;
6665
+ }
6666
+ const renderMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/render$/);
6667
+ if (request.method === "POST" && renderMatch) {
6668
+ const sessionId = safeSessionId(renderMatch[1] ?? "");
6669
+ if (!sessionId) {
6670
+ sendJson2(response, 400, { error: "Invalid session id." });
6671
+ return;
6672
+ }
6673
+ try {
6674
+ const result = renderSession(cwd, sessionId);
6675
+ sendJson2(response, 200, {
6676
+ rendered: true,
6677
+ sessionId: result.sessionId,
6678
+ sessionPath: result.sessionPath,
6679
+ files: [`${result.sessionPath}/index.md`, `${result.sessionPath}/transcript.md`]
6680
+ });
6681
+ } catch (error) {
6682
+ sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
6683
+ }
6684
+ return;
6685
+ }
6621
6686
  sendJson2(response, 404, { error: "Not found." });
6622
6687
  }
6623
6688
  function listen2(server, host, port) {