@appsforgood/next-supabase-kit 0.1.7 → 0.2.0

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 (49) hide show
  1. package/BEST_PRACTICE_EVIDENCE.md +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/DOGFOOD.md +12 -1
  4. package/README.md +33 -4
  5. package/REPOSITORY_SETTINGS.md +12 -6
  6. package/SECURITY.md +14 -0
  7. package/SUPPLY_CHAIN.md +15 -14
  8. package/UPGRADE.md +14 -4
  9. package/antigravity/plugin.json +1 -1
  10. package/assistant-adapters/README.md +3 -0
  11. package/assistant-adapters/antigravity.md +2 -0
  12. package/assistant-adapters/claude-code-subagents.md +1 -0
  13. package/assistant-adapters/codex-agents.md +2 -0
  14. package/assistant-adapters/cursor-agent-kit.mdc +2 -0
  15. package/assistant-adapters/cursor-planner.mdc +1 -1
  16. package/assistant-adapters/orchestrator-runtime.md +39 -0
  17. package/checklists/frontend-quality.md +1 -0
  18. package/checklists/ui-detectors.md +6 -0
  19. package/dist/index.js +1813 -713
  20. package/dist/index.js.map +1 -1
  21. package/dist/studio/office/assets/office.css +167 -6
  22. package/dist/studio/office/assets/office.js +298 -4
  23. package/dist/studio/wizard/assets/wizard.js +7 -1
  24. package/examples/next-supabase-installed/.agent-kit/manifest.json +187 -14
  25. package/examples/next-supabase-installed/audit-output.json +405 -380
  26. package/package.json +27 -4
  27. package/prompts/frontend-design-review.md +6 -0
  28. package/prompts/screenshot-review.md +2 -1
  29. package/schemas/audit-report-v2.schema.json +71 -0
  30. package/schemas/audit-report.schema.json +16 -16
  31. package/schemas/orchestrator.schema.json +167 -0
  32. package/schemas/runtime-event.schema.json +66 -0
  33. package/schemas/runtime-run.schema.json +58 -0
  34. package/schemas/session-event.schema.json +2 -0
  35. package/templates/next-supabase/.agent-kit/orchestrator.json +48 -0
  36. package/templates/next-supabase/.agent-kit/runtime/gitignore.template +2 -0
  37. package/templates/next-supabase/AGENTS.md +2 -0
  38. package/templates/next-supabase/ASSISTANT_ADAPTERS.md +20 -0
  39. package/templates/next-supabase/DECISIONS.md +14 -0
  40. package/templates/next-supabase/DEPLOYMENT.md +4 -0
  41. package/templates/next-supabase/DESIGN.md +6 -0
  42. package/templates/next-supabase/DOCS.md +8 -0
  43. package/templates/next-supabase/MODEL_ROUTING.md +9 -0
  44. package/templates/next-supabase/QUALITY_GATES.md +5 -1
  45. package/templates/next-supabase/SECURITY.md +10 -0
  46. package/templates/next-supabase/SPEC.md +3 -1
  47. package/templates/next-supabase/STYLE_GUIDE.md +15 -0
  48. package/templates/next-supabase/TESTING.md +6 -0
  49. package/templates/next-supabase/UPGRADE.md +1 -0
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { existsSync as existsSync23, readFileSync as readFileSync24, writeFileSync as writeFileSync2 } from "fs";
5
- import { join as join27 } from "path";
4
+ import { existsSync as existsSync26, mkdirSync as mkdirSync3, readFileSync as readFileSync26, writeFileSync as writeFileSync3 } from "fs";
5
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join30, relative as relative2, resolve as resolve3 } from "path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/install/add-skill.ts
@@ -10,8 +10,8 @@ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
10
10
  import { join as join3 } from "path";
11
11
 
12
12
  // src/utils/fs.ts
13
- import { createHash } from "crypto";
14
- import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
13
+ import { createHash, randomUUID } from "crypto";
14
+ import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "fs";
15
15
  import { basename, dirname, isAbsolute, join, relative, resolve } from "path";
16
16
  function ensureDir(path) {
17
17
  mkdirSync(path, { recursive: true });
@@ -22,7 +22,13 @@ function readTextIfExists(path) {
22
22
  }
23
23
  function writeText(path, content) {
24
24
  ensureDir(dirname(path));
25
- writeFileSync(path, content);
25
+ const temporaryPath = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
26
+ try {
27
+ writeFileSync(temporaryPath, content);
28
+ renameSync(temporaryPath, path);
29
+ } finally {
30
+ rmSync(temporaryPath, { force: true });
31
+ }
26
32
  }
27
33
  function sha256(content) {
28
34
  return createHash("sha256").update(content).digest("hex");
@@ -60,6 +66,36 @@ function listFilesRecursive(root) {
60
66
  visit(root);
61
67
  return out.sort();
62
68
  }
69
+ function writeConflictProposal(targetRoot, targetRelativePath, proposedContent, options = {}) {
70
+ const proposalHash = sha256(proposedContent);
71
+ const safeTarget = targetRelativePath.replace(/[^a-zA-Z0-9_.-]/g, "__");
72
+ const baseName = `${safeTarget}.${proposalHash.slice(0, 12)}`;
73
+ const conflictRoot = join(targetRoot, ".agent-kit", "conflicts");
74
+ const proposalPath = join(conflictRoot, `${baseName}.proposed`);
75
+ const metadataPath = join(conflictRoot, `${baseName}.json`);
76
+ const normalizedTarget = targetRelativePath.replace(/\\/g, "/");
77
+ writeText(proposalPath, proposedContent);
78
+ writeText(
79
+ metadataPath,
80
+ `${JSON.stringify(
81
+ {
82
+ schemaVersion: 1,
83
+ target: normalizedTarget,
84
+ proposedSha256: proposalHash,
85
+ ...options.currentContent !== void 0 ? { currentSha256: sha256(options.currentContent) } : {},
86
+ ...options.reason ? { reason: options.reason } : {},
87
+ ...options.sourceVersion ? { sourceVersion: options.sourceVersion } : {}
88
+ },
89
+ null,
90
+ 2
91
+ )}
92
+ `
93
+ );
94
+ return {
95
+ conflictPath: relative(targetRoot, proposalPath).replace(/\\/g, "/"),
96
+ metadataPath: relative(targetRoot, metadataPath).replace(/\\/g, "/")
97
+ };
98
+ }
63
99
  function copyTextWithConflict(sourcePath, targetRoot, targetRelativePath, options = {}) {
64
100
  const targetPath = resolveInside(targetRoot, targetRelativePath);
65
101
  const sourceContent = readFileSync(sourcePath, "utf8");
@@ -75,24 +111,20 @@ function copyTextWithConflict(sourcePath, targetRoot, targetRelativePath, option
75
111
  writeText(targetPath, sourceContent);
76
112
  return { action: "overwritten", target: targetRelativePath };
77
113
  }
78
- const conflictRoot = options.conflictRoot ?? join(targetRoot, ".agent-kit", "conflicts");
79
- const safeName = `${Date.now()}-${targetRelativePath.replace(/[^a-zA-Z0-9_.-]/g, "_")}`;
80
- const conflictPath = join(conflictRoot, safeName);
81
- writeText(conflictPath, sourceContent);
114
+ const defaultConflictRoot = join(targetRoot, ".agent-kit", "conflicts");
115
+ if (options.conflictRoot && resolve(options.conflictRoot) !== resolve(defaultConflictRoot)) {
116
+ throw new Error("Custom conflict roots are no longer supported; proposals must stay under .agent-kit/conflicts.");
117
+ }
118
+ const proposal = writeConflictProposal(targetRoot, targetRelativePath, sourceContent, {
119
+ currentContent: existingContent,
120
+ reason: "Local content differs from the proposed package content."
121
+ });
82
122
  return {
83
123
  action: "conflict",
84
124
  target: targetRelativePath,
85
- conflictPath: relative(targetRoot, conflictPath)
125
+ conflictPath: proposal.conflictPath
86
126
  };
87
127
  }
88
- function copyDirectory(sourceRoot, targetRoot) {
89
- ensureDir(dirname(targetRoot));
90
- cpSync(sourceRoot, targetRoot, {
91
- recursive: true,
92
- force: true,
93
- filter: (source) => !basename(source).startsWith(".DS_Store")
94
- });
95
- }
96
128
 
97
129
  // src/utils/package-root.ts
98
130
  import { existsSync as existsSync2 } from "fs";
@@ -145,12 +177,12 @@ function addSkill(cwd, skillName, options = {}) {
145
177
  }
146
178
 
147
179
  // src/install/adapter-validate.ts
148
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
149
- import { join as join12, normalize } from "path";
180
+ import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
181
+ import { join as join14, normalize } from "path";
150
182
 
151
183
  // src/config/defaults.ts
152
184
  var PACKAGE_NAME = "@appsforgood/next-supabase-kit";
153
- var PACKAGE_VERSION = "0.1.7";
185
+ var PACKAGE_VERSION = "0.2.0";
154
186
  var DEFAULT_CONFIG = {
155
187
  stack: "next-supabase",
156
188
  projectType: "saas",
@@ -210,6 +242,10 @@ var DEFAULT_AGENT_ROSTER_SOURCE = "rosters/next-supabase-default-council.json";
210
242
  var DEFAULT_AGENT_ROSTER_TARGET = ".agent-kit/agent-roster.json";
211
243
  var DEFAULT_MODEL_ROUTING_SOURCE = "model-routing/default-model-routing.json";
212
244
  var DEFAULT_MODEL_ROUTING_TARGET = ".agent-kit/model-routing.json";
245
+ var DEFAULT_ORCHESTRATOR_SOURCE = "templates/next-supabase/.agent-kit/orchestrator.json";
246
+ var DEFAULT_ORCHESTRATOR_TARGET = ".agent-kit/orchestrator.json";
247
+ var DEFAULT_RUNTIME_IGNORE_SOURCE = "templates/next-supabase/.agent-kit/runtime/gitignore.template";
248
+ var DEFAULT_RUNTIME_IGNORE_TARGET = ".agent-kit/runtime/.gitignore";
213
249
  var CURSOR_ADAPTER_FILES = [
214
250
  {
215
251
  source: "assistant-adapters/cursor-agent-kit.mdc",
@@ -268,7 +304,7 @@ var CI_TEMPLATE_FILES = [
268
304
  ];
269
305
 
270
306
  // src/studio/shared.ts
271
- import { appendFileSync, existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
307
+ import { appendFileSync, closeSync, existsSync as existsSync4, openSync, readFileSync as readFileSync3, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
272
308
  import { basename as basename2, dirname as dirname3, join as join4 } from "path";
273
309
  var AGENT_KIT_DIR = ".agent-kit";
274
310
  var CONTEXT_JSON = ".agent-kit/project-context.json";
@@ -284,8 +320,15 @@ var SECRET_PATTERNS = [
284
320
  /gh[pousr]_[A-Za-z0-9_]{12,}/g,
285
321
  /github_pat_[A-Za-z0-9_]{20,}/g,
286
322
  /sk_(?:live|test)_[A-Za-z0-9_]{8,}/g,
323
+ /sk-(?:proj-|svcacct-|ant-api\d{2}-)?[A-Za-z0-9_-]{20,}/g,
324
+ /xai-[A-Za-z0-9_-]{20,}/g,
325
+ /AIza[0-9A-Za-z_-]{30,}/g,
326
+ /AKIA[0-9A-Z]{16}/g,
327
+ /(?:xox[baprs]-)[A-Za-z0-9-]{10,}/g,
328
+ /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
329
+ /\bBearer\s+[A-Za-z0-9._~+/-]{20,}=*/gi,
287
330
  /sbp_[A-Za-z0-9_]{12,}/g,
288
- /(?:SUPABASE_SERVICE_ROLE_KEY|DATABASE_URL|OPENAI_API_KEY|ANTHROPIC_API_KEY|GITHUB_TOKEN)=(?!\\n|\\r|\\r\\n)["']?[A-Za-z0-9][A-Za-z0-9_./+:-]+/gi,
331
+ /\b(?:[A-Z0-9_]*(?:API_KEY|TOKEN|SECRET|PASSWORD|PRIVATE_KEY)|DATABASE_URL)\s*=\s*(?!\\n|\\r|\\r\\n)["']?(?!(?:process\.env|import\.meta\.env|env:|keychain:|\$\{|<|replace|example|your|dummy|fake))[^\\\s"'`]{8,}/g,
289
332
  /postgres(?:ql)?:\/\/[^\s)]+/gi
290
333
  ];
291
334
  function nowIso() {
@@ -300,6 +343,20 @@ function containsLikelySecret(text) {
300
343
  return pattern.test(text);
301
344
  });
302
345
  }
346
+ function assertNoLikelySecret(value, label = "Persisted value") {
347
+ const serialized = typeof value === "string" ? value : JSON.stringify(value);
348
+ if (containsLikelySecret(serialized)) {
349
+ throw new Error(`${label} appears to contain a secret. Store credentials in an environment variable or OS keychain reference instead.`);
350
+ }
351
+ }
352
+ function redactValue(value) {
353
+ if (typeof value === "string") return redactSensitive(value);
354
+ if (Array.isArray(value)) return value.map(redactValue);
355
+ if (value && typeof value === "object") {
356
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item)]));
357
+ }
358
+ return value;
359
+ }
303
360
  function safeSlug(input) {
304
361
  const lowered = input.trim().toLowerCase();
305
362
  if (!lowered) throw new Error("A non-empty title or id is required.");
@@ -322,23 +379,73 @@ function readJsonFile(cwd, relativePath) {
322
379
  return JSON.parse(readFileSync3(path, "utf8"));
323
380
  }
324
381
  function writeJsonFile(cwd, relativePath, value) {
382
+ assertNoLikelySecret(value, relativePath);
325
383
  writeText(resolveInside(cwd, relativePath), `${JSON.stringify(value, null, 2)}
326
384
  `);
327
385
  }
328
386
  function appendJsonLine(cwd, relativePath, value) {
329
387
  const path = resolveInside(cwd, relativePath);
330
388
  ensureDir(dirname3(path));
331
- appendFileSync(path, `${JSON.stringify(value)}
389
+ appendFileSync(path, `${JSON.stringify(redactValue(value))}
332
390
  `);
333
391
  }
334
392
  function readJsonLines(cwd, relativePath) {
335
393
  const text = readTextIfExists(resolveInside(cwd, relativePath));
336
394
  if (!text) return [];
337
- return text.split(/\r?\n/).filter((line2) => line2.trim().length > 0).map((line2) => JSON.parse(line2));
395
+ const lines = text.split(/\r?\n/);
396
+ let lastContentIndex = -1;
397
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
398
+ if (lines[index]?.trim()) {
399
+ lastContentIndex = index;
400
+ break;
401
+ }
402
+ }
403
+ return lines.flatMap((line2, index) => {
404
+ if (!line2.trim()) return [];
405
+ try {
406
+ return [JSON.parse(line2)];
407
+ } catch (error) {
408
+ if (index === lastContentIndex && !text.endsWith("\n") && !text.endsWith("\r")) return [];
409
+ throw error;
410
+ }
411
+ });
338
412
  }
339
413
  function writeTextFile(cwd, relativePath, content) {
414
+ assertNoLikelySecret(content, relativePath);
340
415
  writeText(resolveInside(cwd, relativePath), content);
341
416
  }
417
+ var LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4));
418
+ function withFileLock(cwd, relativeLockPath, operation) {
419
+ const lockPath = resolveInside(cwd, relativeLockPath);
420
+ ensureDir(dirname3(lockPath));
421
+ const deadline = Date.now() + 5e3;
422
+ let descriptor;
423
+ while (descriptor === void 0) {
424
+ try {
425
+ descriptor = openSync(lockPath, "wx", 384);
426
+ writeFileSync2(descriptor, `${process.pid} ${Date.now()}
427
+ `);
428
+ } catch (error) {
429
+ const code = error instanceof Error && "code" in error ? error.code : void 0;
430
+ if (code !== "EEXIST") throw error;
431
+ try {
432
+ if (Date.now() - statSync(lockPath).mtimeMs > 3e4) unlinkSync(lockPath);
433
+ } catch {
434
+ }
435
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for local file lock: ${relativeLockPath}`);
436
+ Atomics.wait(LOCK_SLEEP, 0, 0, 15);
437
+ }
438
+ }
439
+ try {
440
+ return operation();
441
+ } finally {
442
+ closeSync(descriptor);
443
+ try {
444
+ unlinkSync(lockPath);
445
+ } catch {
446
+ }
447
+ }
448
+ }
342
449
  function readTextFile(cwd, relativePath) {
343
450
  return readTextIfExists(resolveInside(cwd, relativePath));
344
451
  }
@@ -362,10 +469,38 @@ function listMarkdown(items) {
362
469
  function unique(values) {
363
470
  return [...new Set(values.filter(Boolean))].sort();
364
471
  }
472
+ function readJsonBody(request) {
473
+ return new Promise((resolve4, reject) => {
474
+ const chunks = [];
475
+ let bodyTooLarge = false;
476
+ request.on("data", (chunk) => {
477
+ if (bodyTooLarge) return;
478
+ chunks.push(chunk);
479
+ if (chunks.reduce((total, item) => total + item.length, 0) > 256e3) {
480
+ bodyTooLarge = true;
481
+ reject(new Error("Request body too large."));
482
+ }
483
+ });
484
+ request.on("end", () => {
485
+ if (bodyTooLarge) return;
486
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
487
+ if (!raw) {
488
+ resolve4({});
489
+ return;
490
+ }
491
+ try {
492
+ resolve4(JSON.parse(raw));
493
+ } catch {
494
+ reject(new Error("Request body must be valid JSON."));
495
+ }
496
+ });
497
+ request.on("error", reject);
498
+ });
499
+ }
365
500
 
366
501
  // src/install/audit.ts
367
- import { existsSync as existsSync11, readFileSync as readFileSync9, statSync } from "fs";
368
- import { join as join11 } from "path";
502
+ import { existsSync as existsSync12, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
503
+ import { join as join13 } from "path";
369
504
 
370
505
  // src/config/contracts.ts
371
506
  import { z } from "zod";
@@ -580,6 +715,8 @@ var StudioSessionContract = z.object({
580
715
  renderedAt: z.string().datetime().optional()
581
716
  }).strict();
582
717
  var SessionEventContract = z.object({
718
+ eventId: z.string().uuid().optional(),
719
+ sequence: z.number().int().positive().optional(),
583
720
  type: z.enum([
584
721
  "session_started",
585
722
  "project_context_loaded",
@@ -1141,8 +1278,8 @@ function onboardingStateExists(cwd) {
1141
1278
  }
1142
1279
 
1143
1280
  // src/install/install.ts
1144
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1145
- import { join as join10 } from "path";
1281
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
1282
+ import { join as join11 } from "path";
1146
1283
 
1147
1284
  // src/install/ide-activate.ts
1148
1285
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
@@ -1314,10 +1451,11 @@ function writeGeneratedAgentFile(cwd, relativePath, content, force, result) {
1314
1451
  result.unchanged.push(relativePath);
1315
1452
  return;
1316
1453
  }
1317
- const conflictPath = join8(cwd, ".agent-kit", "conflicts", relativePath.replace(/\//g, "__"));
1318
- ensureDir(join8(cwd, ".agent-kit", "conflicts"));
1319
- writeText(conflictPath, existing);
1320
- result.conflicts.push(`${relativePath} -> ${conflictPath}`);
1454
+ const proposal = writeConflictProposal(cwd, relativePath, content, {
1455
+ currentContent: existing,
1456
+ reason: "Generated agent content changed while the local target is customized."
1457
+ });
1458
+ result.conflicts.push(`${relativePath} -> ${proposal.conflictPath}`);
1321
1459
  return;
1322
1460
  }
1323
1461
  ensureDir(join8(cwd, relativePath.split("/").slice(0, -1).join("/")));
@@ -1585,17 +1723,57 @@ function ideSurfaceToActivateTarget(ideSurface) {
1585
1723
  return null;
1586
1724
  }
1587
1725
 
1726
+ // src/install/managed-assets.ts
1727
+ import { readFileSync as readFileSync8 } from "fs";
1728
+ import { join as join10 } from "path";
1729
+ function listManagedAssets(packageRoot, stack) {
1730
+ const assets = [];
1731
+ const templateRoot = join10(packageRoot, "templates", stack);
1732
+ for (const target of ROOT_DOCS) {
1733
+ assets.push({ target, sourcePath: join10(templateRoot, target), category: "root-doc" });
1734
+ }
1735
+ for (const adapter2 of CURSOR_ADAPTER_FILES) {
1736
+ assets.push({ target: adapter2.target, sourcePath: join10(packageRoot, adapter2.source), category: "adapter" });
1737
+ }
1738
+ assets.push(
1739
+ { target: DEFAULT_AGENT_ROSTER_TARGET, sourcePath: join10(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE), category: "roster" },
1740
+ { target: DEFAULT_MODEL_ROUTING_TARGET, sourcePath: join10(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE), category: "model-routing" },
1741
+ { target: DEFAULT_ORCHESTRATOR_TARGET, sourcePath: join10(packageRoot, DEFAULT_ORCHESTRATOR_SOURCE), category: "orchestrator" },
1742
+ { target: DEFAULT_RUNTIME_IGNORE_TARGET, sourcePath: join10(packageRoot, DEFAULT_RUNTIME_IGNORE_SOURCE), category: "orchestrator" }
1743
+ );
1744
+ for (const template of CI_TEMPLATE_FILES) {
1745
+ assets.push({ target: template.target, sourcePath: join10(packageRoot, template.source), category: "ci" });
1746
+ }
1747
+ for (const folder of LIBRARY_FOLDERS) {
1748
+ for (const relativePath of listFilesRecursive(join10(packageRoot, folder))) {
1749
+ assets.push({
1750
+ target: `.agent-kit/${folder}/${relativePath}`.replace(/\\/g, "/"),
1751
+ sourcePath: join10(packageRoot, folder, relativePath),
1752
+ category: "library",
1753
+ libraryFolder: folder
1754
+ });
1755
+ }
1756
+ }
1757
+ const byTarget = /* @__PURE__ */ new Map();
1758
+ for (const asset of assets) byTarget.set(asset.target, asset);
1759
+ return [...byTarget.values()].sort((left, right) => left.target.localeCompare(right.target));
1760
+ }
1761
+ function hashManagedAssets(assets) {
1762
+ return Object.fromEntries(assets.map((asset) => [asset.target, sha256(readFileSync8(asset.sourcePath, "utf8"))]));
1763
+ }
1764
+
1588
1765
  // src/install/install.ts
1589
1766
  function initProject(options) {
1590
1767
  const cwd = options.cwd;
1591
1768
  const stack = options.stack ?? DEFAULT_CONFIG.stack;
1592
1769
  const packageRoot = findPackageRoot();
1593
- const templateRoot = join10(packageRoot, "templates", stack);
1770
+ const templateRoot = join11(packageRoot, "templates", stack);
1771
+ const managedAssets = listManagedAssets(packageRoot, stack);
1594
1772
  if (!existsSync10(templateRoot)) {
1595
1773
  throw new Error(`Unsupported stack profile: ${stack}`);
1596
1774
  }
1597
- ensureDir(join10(cwd, ".agent-kit"));
1598
- ensureDir(join10(cwd, ".agent-kit", "conflicts"));
1775
+ ensureDir(join11(cwd, ".agent-kit"));
1776
+ ensureDir(join11(cwd, ".agent-kit", "conflicts"));
1599
1777
  const result = {
1600
1778
  copied: [],
1601
1779
  unchanged: [],
@@ -1605,11 +1783,11 @@ function initProject(options) {
1605
1783
  };
1606
1784
  const templateHashes = {};
1607
1785
  for (const doc of ROOT_DOCS) {
1608
- const templatePath = join10(templateRoot, doc);
1609
- templateHashes[doc] = sha256(readFileSync8(templatePath, "utf8"));
1786
+ const templatePath = join11(templateRoot, doc);
1787
+ templateHashes[doc] = sha256(readFileSync9(templatePath, "utf8"));
1610
1788
  const copyResult = copyTextWithConflict(templatePath, cwd, doc, {
1611
1789
  force: Boolean(options.force),
1612
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1790
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1613
1791
  });
1614
1792
  if (copyResult.action === "created") result.copied.push(copyResult.target);
1615
1793
  if (copyResult.action === "unchanged") result.unchanged.push(copyResult.target);
@@ -1618,13 +1796,18 @@ function initProject(options) {
1618
1796
  result.conflicts.push(`${copyResult.target} -> ${copyResult.conflictPath}`);
1619
1797
  }
1620
1798
  }
1621
- for (const folder of LIBRARY_FOLDERS) {
1622
- copyDirectory(join10(packageRoot, folder), join10(cwd, ".agent-kit", folder));
1799
+ for (const asset of managedAssets.filter((item) => item.category === "library")) {
1800
+ const libraryCopy = copyTextWithConflict(asset.sourcePath, cwd, asset.target, {
1801
+ force: Boolean(options.force),
1802
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1803
+ });
1804
+ if (libraryCopy.action === "overwritten") result.overwritten.push(libraryCopy.target);
1805
+ if (libraryCopy.action === "conflict") result.conflicts.push(`${libraryCopy.target} -> ${libraryCopy.conflictPath}`);
1623
1806
  }
1624
1807
  for (const adapter2 of CURSOR_ADAPTER_FILES) {
1625
- const adapterCopy = copyTextWithConflict(join10(packageRoot, adapter2.source), cwd, adapter2.target, {
1808
+ const adapterCopy = copyTextWithConflict(join11(packageRoot, adapter2.source), cwd, adapter2.target, {
1626
1809
  force: Boolean(options.force),
1627
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1810
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1628
1811
  });
1629
1812
  if (adapterCopy.action === "created") result.copied.push(adapterCopy.target);
1630
1813
  if (adapterCopy.action === "unchanged") result.unchanged.push(adapterCopy.target);
@@ -1633,23 +1816,40 @@ function initProject(options) {
1633
1816
  result.conflicts.push(`${adapterCopy.target} -> ${adapterCopy.conflictPath}`);
1634
1817
  }
1635
1818
  }
1636
- const rosterCopy = copyTextWithConflict(join10(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE), cwd, DEFAULT_AGENT_ROSTER_TARGET, {
1819
+ const rosterCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE), cwd, DEFAULT_AGENT_ROSTER_TARGET, {
1637
1820
  force: Boolean(options.force),
1638
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1821
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1639
1822
  });
1640
1823
  if (rosterCopy.action === "created") result.copied.push(rosterCopy.target);
1641
1824
  if (rosterCopy.action === "unchanged") result.unchanged.push(rosterCopy.target);
1642
1825
  if (rosterCopy.action === "overwritten") result.overwritten.push(rosterCopy.target);
1643
1826
  if (rosterCopy.action === "conflict") result.conflicts.push(`${rosterCopy.target} -> ${rosterCopy.conflictPath}`);
1644
- const modelRoutingCopy = copyTextWithConflict(join10(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE), cwd, DEFAULT_MODEL_ROUTING_TARGET, {
1827
+ const modelRoutingCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE), cwd, DEFAULT_MODEL_ROUTING_TARGET, {
1645
1828
  force: Boolean(options.force),
1646
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1829
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1647
1830
  });
1648
1831
  if (modelRoutingCopy.action === "created") result.copied.push(modelRoutingCopy.target);
1649
1832
  if (modelRoutingCopy.action === "unchanged") result.unchanged.push(modelRoutingCopy.target);
1650
1833
  if (modelRoutingCopy.action === "overwritten") result.overwritten.push(modelRoutingCopy.target);
1651
1834
  if (modelRoutingCopy.action === "conflict") result.conflicts.push(`${modelRoutingCopy.target} -> ${modelRoutingCopy.conflictPath}`);
1835
+ const orchestratorCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_ORCHESTRATOR_SOURCE), cwd, DEFAULT_ORCHESTRATOR_TARGET, {
1836
+ force: Boolean(options.force),
1837
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1838
+ });
1839
+ if (orchestratorCopy.action === "created") result.copied.push(orchestratorCopy.target);
1840
+ if (orchestratorCopy.action === "unchanged") result.unchanged.push(orchestratorCopy.target);
1841
+ if (orchestratorCopy.action === "overwritten") result.overwritten.push(orchestratorCopy.target);
1842
+ if (orchestratorCopy.action === "conflict") result.conflicts.push(`${orchestratorCopy.target} -> ${orchestratorCopy.conflictPath}`);
1843
+ const runtimeIgnoreCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_RUNTIME_IGNORE_SOURCE), cwd, DEFAULT_RUNTIME_IGNORE_TARGET, {
1844
+ force: Boolean(options.force),
1845
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1846
+ });
1847
+ if (runtimeIgnoreCopy.action === "created") result.copied.push(runtimeIgnoreCopy.target);
1848
+ if (runtimeIgnoreCopy.action === "unchanged") result.unchanged.push(runtimeIgnoreCopy.target);
1849
+ if (runtimeIgnoreCopy.action === "overwritten") result.overwritten.push(runtimeIgnoreCopy.target);
1850
+ if (runtimeIgnoreCopy.action === "conflict") result.conflicts.push(`${runtimeIgnoreCopy.target} -> ${runtimeIgnoreCopy.conflictPath}`);
1652
1851
  const manifest = {
1852
+ schemaVersion: 2,
1653
1853
  packageName: PACKAGE_NAME,
1654
1854
  packageVersion: PACKAGE_VERSION,
1655
1855
  stack,
@@ -1658,19 +1858,20 @@ function initProject(options) {
1658
1858
  libraryFolders: [...LIBRARY_FOLDERS],
1659
1859
  agentRoster: DEFAULT_AGENT_ROSTER_TARGET,
1660
1860
  modelRouting: DEFAULT_MODEL_ROUTING_TARGET,
1661
- templateHashes
1861
+ templateHashes,
1862
+ assetHashes: hashManagedAssets(managedAssets)
1662
1863
  };
1663
- writeText(join10(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(manifest, null, 2)}
1864
+ writeText(join11(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(manifest, null, 2)}
1664
1865
  `);
1665
- writeText(join10(cwd, ".agent-kit", "config.json"), `${JSON.stringify(DEFAULT_CONFIG, null, 2)}
1866
+ writeText(join11(cwd, ".agent-kit", "config.json"), `${JSON.stringify(DEFAULT_CONFIG, null, 2)}
1666
1867
  `);
1667
- const overridesPath = join10(cwd, ".agent-kit", "overrides.json");
1868
+ const overridesPath = join11(cwd, ".agent-kit", "overrides.json");
1668
1869
  if (!existsSync10(overridesPath)) writeText(overridesPath, `${JSON.stringify({ templates: {} }, null, 2)}
1669
1870
  `);
1670
1871
  for (const template of CI_TEMPLATE_FILES) {
1671
- const ciCopy = copyTextWithConflict(join10(packageRoot, template.source), cwd, template.target, {
1872
+ const ciCopy = copyTextWithConflict(join11(packageRoot, template.source), cwd, template.target, {
1672
1873
  force: Boolean(options.force),
1673
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1874
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1674
1875
  });
1675
1876
  if (ciCopy.action === "created") result.copied.push(ciCopy.target);
1676
1877
  if (ciCopy.action === "unchanged") result.unchanged.push(ciCopy.target);
@@ -1694,10 +1895,278 @@ function initProject(options) {
1694
1895
  return result;
1695
1896
  }
1696
1897
  function readManifest(cwd) {
1697
- const manifestPath = join10(cwd, ".agent-kit", "manifest.json");
1898
+ const manifestPath = join11(cwd, ".agent-kit", "manifest.json");
1698
1899
  if (!existsSync10(manifestPath)) return null;
1699
- return JSON.parse(readFileSync8(manifestPath, "utf8"));
1900
+ return JSON.parse(readFileSync9(manifestPath, "utf8"));
1901
+ }
1902
+
1903
+ // src/install/audit-rules/project-reality.ts
1904
+ import { execFileSync } from "child_process";
1905
+ import { existsSync as existsSync11, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
1906
+ import { join as join12 } from "path";
1907
+
1908
+ // src/install/audit-rules/types.ts
1909
+ var AuditRuleRegistry = class {
1910
+ rules = /* @__PURE__ */ new Map();
1911
+ register(rule) {
1912
+ if (this.rules.has(rule.id)) throw new Error(`Duplicate audit rule id: ${rule.id}`);
1913
+ this.rules.set(rule.id, rule);
1914
+ return this;
1915
+ }
1916
+ evaluate(context2) {
1917
+ return [...this.rules.values()].flatMap((rule) => {
1918
+ const result = rule.evaluate(context2);
1919
+ if (!result) return [];
1920
+ const findings = Array.isArray(result) ? result : [result];
1921
+ return findings.map((finding) => ({
1922
+ ...finding,
1923
+ area: finding.area || rule.area,
1924
+ ruleId: rule.id,
1925
+ ruleVersion: rule.version,
1926
+ ...rule.helpUri ? { helpUri: rule.helpUri } : {}
1927
+ }));
1928
+ });
1929
+ }
1930
+ };
1931
+
1932
+ // src/install/audit-rules/project-reality.ts
1933
+ function relativeFilesFromGit(cwd) {
1934
+ try {
1935
+ const output = execFileSync("git", ["ls-files", "-z", "--cached"], {
1936
+ cwd,
1937
+ encoding: "utf8",
1938
+ stdio: ["ignore", "pipe", "ignore"]
1939
+ });
1940
+ return { files: output.split("\0").filter(Boolean), tracked: true };
1941
+ } catch {
1942
+ return { files: listFilesRecursive(cwd), tracked: false };
1943
+ }
1944
+ }
1945
+ function readPackageJson2(cwd) {
1946
+ const path = join12(cwd, "package.json");
1947
+ if (!existsSync11(path)) return null;
1948
+ try {
1949
+ return JSON.parse(readFileSync10(path, "utf8"));
1950
+ } catch {
1951
+ return null;
1952
+ }
1953
+ }
1954
+ function testScriptLooksExecutable(script) {
1955
+ if (/\b(?:echo|printf)\b.*\b(?:no|skip|todo|placeholder)\b.*\btests?\b/i.test(script)) return false;
1956
+ if (/^(?:true|exit\s+0|:)(?:\s|$)/i.test(script.trim())) return false;
1957
+ return /\b(?:vitest|jest|playwright|node\s+--test|tsx?\s+--test|pytest|cargo\s+test|go\s+test|flutter\s+test|dart\s+test|mvn\s+test|gradle\w*\s+test)\b/i.test(
1958
+ script
1959
+ );
1960
+ }
1961
+ function normalizeSqlIdentifier(identifier) {
1962
+ return identifier.split(".").map((part) => part.replace(/^"|"$/g, "").toLowerCase()).join(".");
1963
+ }
1964
+ function collectSqlIdentifiers(sql, expression) {
1965
+ const values = /* @__PURE__ */ new Set();
1966
+ for (const match of sql.matchAll(expression)) {
1967
+ if (match[1]) values.add(normalizeSqlIdentifier(match[1]));
1968
+ }
1969
+ return values;
1970
+ }
1971
+ function containsLikelySecretForAudit(relativeFile, content) {
1972
+ const normalized = relativeFile.replace(/\\/g, "/");
1973
+ const testSecretFixture = ["sk", "test", "fake", "secret", "value"].join("_");
1974
+ let candidate = content.replace(/gh[pousr]_(?:replace|example|fake|dummy)[A-Za-z0-9_]*/gi, "x").replaceAll(testSecretFixture, "x").replaceAll("sk-proj-this-is-a-resolved-secret-value", "x").replace(/sk-testsecret[A-Za-z0-9_-]*/g, "x");
1975
+ if (normalized.startsWith("tests/") && content.includes("not.toContain(fakeSecret)")) candidate = candidate.replaceAll("top-secret-value", "x");
1976
+ return containsLikelySecret(candidate);
1977
+ }
1978
+ function evaluateRls(context2) {
1979
+ const migrationsDir = join12(context2.cwd, "supabase", "migrations");
1980
+ if (!existsSync11(migrationsDir)) return null;
1981
+ const sqlFiles = listFilesRecursive(migrationsDir).filter((file) => file.endsWith(".sql"));
1982
+ if (sqlFiles.length === 0) {
1983
+ return {
1984
+ level: "warn",
1985
+ area: "project-reality",
1986
+ message: "supabase/migrations exists but contains no SQL migration files.",
1987
+ remediation: "Add versioned SQL migrations or remove the empty migrations directory if Supabase is not in use.",
1988
+ confidence: "high",
1989
+ evidence: [{ kind: "file", path: "supabase/migrations", summary: "Migration directory is empty.", observedAt: context2.observedAt }]
1990
+ };
1991
+ }
1992
+ const created = /* @__PURE__ */ new Set();
1993
+ const rlsEnabled = /* @__PURE__ */ new Set();
1994
+ const policyTables = /* @__PURE__ */ new Set();
1995
+ for (const file of sqlFiles) {
1996
+ const sql = readFileSync10(join12(migrationsDir, file), "utf8");
1997
+ for (const table of collectSqlIdentifiers(sql, /create\s+table\s+(?:if\s+not\s+exists\s+)?((?:"?[a-zA-Z_][\w$-]*"?\.)?"?[a-zA-Z_][\w$-]*"?)/gi))
1998
+ created.add(table);
1999
+ for (const table of collectSqlIdentifiers(
2000
+ sql,
2001
+ /alter\s+table\s+(?:only\s+)?((?:"?[a-zA-Z_][\w$-]*"?\.)?"?[a-zA-Z_][\w$-]*"?)\s+enable\s+row\s+level\s+security/gi
2002
+ ))
2003
+ rlsEnabled.add(table);
2004
+ for (const table of collectSqlIdentifiers(sql, /create\s+policy\s+[\s\S]*?\s+on\s+((?:"?[a-zA-Z_][\w$-]*"?\.)?"?[a-zA-Z_][\w$-]*"?)/gi))
2005
+ policyTables.add(table);
2006
+ }
2007
+ const missingRls = [...created].filter((table) => !rlsEnabled.has(table)).sort();
2008
+ if (missingRls.length > 0 || rlsEnabled.size === 0) {
2009
+ return {
2010
+ level: "fail",
2011
+ area: "project-reality",
2012
+ message: missingRls.length > 0 ? `Migration-created tables missing explicit row level security (RLS) enablement: ${missingRls.join(", ")}.` : "No Supabase migration enables row level security.",
2013
+ remediation: "Enable RLS explicitly for every application table and add intentional policies before shipping user data.",
2014
+ confidence: "high",
2015
+ evidence: sqlFiles.map((file) => ({
2016
+ kind: "file",
2017
+ path: `supabase/migrations/${file}`,
2018
+ summary: "Parsed SQL migration.",
2019
+ observedAt: context2.observedAt
2020
+ }))
2021
+ };
2022
+ }
2023
+ const withoutPolicies = [...rlsEnabled].filter((table) => created.has(table) && !policyTables.has(table)).sort();
2024
+ const findings = [
2025
+ {
2026
+ level: "pass",
2027
+ area: "project-reality",
2028
+ message: `Supabase migrations explicitly enable RLS for ${rlsEnabled.size} table(s).`,
2029
+ confidence: "high",
2030
+ evidence: sqlFiles.map((file) => ({
2031
+ kind: "file",
2032
+ path: `supabase/migrations/${file}`,
2033
+ summary: "Parsed SQL migration.",
2034
+ observedAt: context2.observedAt
2035
+ }))
2036
+ }
2037
+ ];
2038
+ if (withoutPolicies.length > 0) {
2039
+ findings.push({
2040
+ level: "warn",
2041
+ area: "project-reality",
2042
+ message: `RLS-enabled tables without a policy in the scanned migrations: ${withoutPolicies.join(", ")}.`,
2043
+ remediation: "Add explicit policies or document why access should remain denied by default.",
2044
+ confidence: "medium",
2045
+ evidence: []
2046
+ });
2047
+ }
2048
+ return findings;
2049
+ }
2050
+ function evaluateTests(context2) {
2051
+ const packageJson = readPackageJson2(context2.cwd);
2052
+ if (!packageJson) {
2053
+ return {
2054
+ level: "warn",
2055
+ area: "project-reality",
2056
+ message: "No package.json found to verify test scripts.",
2057
+ remediation: "Add package.json with executable test, lint, and build scripts appropriate to the stack.",
2058
+ confidence: "high",
2059
+ evidence: []
2060
+ };
2061
+ }
2062
+ const scripts = packageJson.scripts ?? {};
2063
+ const entry = ["test", "test:unit", "test:ci"].find((name) => scripts[name]);
2064
+ if (!entry) {
2065
+ return {
2066
+ level: "warn",
2067
+ area: "project-reality",
2068
+ message: "package.json has no test script (test, test:unit, or test:ci).",
2069
+ remediation: "Add an executable test command and document it in TESTING.md.",
2070
+ confidence: "high",
2071
+ evidence: [{ kind: "file", path: "package.json", summary: "No supported test script key was found.", observedAt: context2.observedAt }]
2072
+ };
2073
+ }
2074
+ const script = scripts[entry] ?? "";
2075
+ const files = listFilesRecursive(context2.cwd);
2076
+ const hasTests = files.some((file) => /(^|\/)(?:tests?|__tests__)(\/|$)|\.(?:test|spec)\.[cm]?[jt]sx?$/i.test(file));
2077
+ if (!testScriptLooksExecutable(script) || !hasTests) {
2078
+ return {
2079
+ level: "warn",
2080
+ area: "project-reality",
2081
+ message: `package.json script ${entry} does not provide credible executable test evidence.`,
2082
+ remediation: "Use a recognized test runner and keep at least one discoverable test file in the repository.",
2083
+ confidence: "high",
2084
+ evidence: [{ kind: "configuration", path: "package.json", summary: `${entry}: ${script}`, observedAt: context2.observedAt }]
2085
+ };
2086
+ }
2087
+ return {
2088
+ level: "pass",
2089
+ area: "project-reality",
2090
+ message: `package.json defines executable test script ${entry} and discoverable test files exist.`,
2091
+ confidence: "high",
2092
+ evidence: [{ kind: "configuration", path: "package.json", summary: `${entry}: ${script}`, observedAt: context2.observedAt }]
2093
+ };
2094
+ }
2095
+ function evaluateSecrets(context2) {
2096
+ const inventory = relativeFilesFromGit(context2.cwd);
2097
+ const candidates = inventory.files.filter((file) => {
2098
+ const normalized = file.replace(/\\/g, "/");
2099
+ if (normalized.includes("node_modules/") || normalized.includes(".agent-kit/")) return false;
2100
+ const path = join12(context2.cwd, file);
2101
+ try {
2102
+ return statSync2(path).isFile() && statSync2(path).size <= 1e6;
2103
+ } catch {
2104
+ return false;
2105
+ }
2106
+ });
2107
+ const hits = candidates.flatMap((file) => {
2108
+ try {
2109
+ const content = readFileSync10(join12(context2.cwd, file), "utf8");
2110
+ if (content.includes("\0")) return [];
2111
+ return containsLikelySecretForAudit(file, content) ? [file.replace(/\\/g, "/")] : [];
2112
+ } catch {
2113
+ return [];
2114
+ }
2115
+ });
2116
+ if (hits.length > 0) {
2117
+ return {
2118
+ level: "fail",
2119
+ area: "project-reality",
2120
+ message: `Possible ${inventory.tracked ? "committed" : "workspace"} secret patterns detected in: ${hits.slice(0, 5).join(", ")}.`,
2121
+ remediation: "Remove secrets, rotate exposed credentials, and store only credential references in project files.",
2122
+ confidence: inventory.tracked ? "high" : "medium",
2123
+ evidence: hits.slice(0, 20).map((path) => ({ kind: "file", path, summary: "Secret-like pattern detected; value omitted.", observedAt: context2.observedAt }))
2124
+ };
2125
+ }
2126
+ if (candidates.length === 0) return null;
2127
+ return {
2128
+ level: "pass",
2129
+ area: "project-reality",
2130
+ message: `No obvious secret patterns detected in ${inventory.tracked ? "Git-tracked" : "workspace"} files.`,
2131
+ confidence: inventory.tracked ? "high" : "medium",
2132
+ evidence: [{ kind: "command", summary: inventory.tracked ? "git ls-files --cached" : "Recursive workspace fallback", observedAt: context2.observedAt }]
2133
+ };
2134
+ }
2135
+ function evaluateContext(context2) {
2136
+ if (context2.packageRepository) {
2137
+ return {
2138
+ level: "pass",
2139
+ area: "project-reality",
2140
+ message: "Package source repository mode does not require installed-project context files.",
2141
+ confidence: "high",
2142
+ evidence: []
2143
+ };
2144
+ }
2145
+ if (!existsSync11(join12(context2.cwd, CONTEXT_JSON))) {
2146
+ return {
2147
+ level: "warn",
2148
+ area: "project-reality",
2149
+ message: ".agent-kit/project-context.json is missing.",
2150
+ remediation: "Run agent-kit init or agent-kit context init to create project context.",
2151
+ confidence: "high",
2152
+ evidence: []
2153
+ };
2154
+ }
2155
+ return {
2156
+ level: "pass",
2157
+ area: "project-reality",
2158
+ message: ".agent-kit/project-context.json exists.",
2159
+ confidence: "high",
2160
+ evidence: [{ kind: "file", path: CONTEXT_JSON, summary: "Project context file exists.", observedAt: context2.observedAt }]
2161
+ };
1700
2162
  }
2163
+ var projectRealityRules = new AuditRuleRegistry().register({
2164
+ id: "project-reality.supabase.rls-per-table",
2165
+ version: "1.0.0",
2166
+ area: "project-reality",
2167
+ helpUri: "https://supabase.com/docs/guides/database/postgres/row-level-security",
2168
+ evaluate: evaluateRls
2169
+ }).register({ id: "project-reality.tests.executable-script", version: "1.0.0", area: "project-reality", evaluate: evaluateTests }).register({ id: "project-reality.secrets.git-tracked", version: "1.0.0", area: "project-reality", evaluate: evaluateSecrets }).register({ id: "project-reality.context.present", version: "1.0.0", area: "project-reality", evaluate: evaluateContext });
1701
2170
 
1702
2171
  // src/install/audit.ts
1703
2172
  var REQUIRED_AGENT_IDS = [
@@ -1742,13 +2211,17 @@ var REQUIRED_SCHEMA_FILES = [
1742
2211
  "agent-roster.schema.json",
1743
2212
  "council-session.schema.json",
1744
2213
  "audit-report.schema.json",
2214
+ "audit-report-v2.schema.json",
1745
2215
  "model-routing.schema.json",
1746
2216
  "project-context.schema.json",
1747
2217
  "correction-rules.schema.json",
1748
2218
  "session-event.schema.json",
1749
2219
  "studio-session.schema.json",
1750
2220
  "onboarding-state.schema.json",
1751
- "agentic-level.schema.json"
2221
+ "agentic-level.schema.json",
2222
+ "orchestrator.schema.json",
2223
+ "runtime-run.schema.json",
2224
+ "runtime-event.schema.json"
1752
2225
  ];
1753
2226
  var COUNCIL_SESSION_DIR = ".agent-kit/council-sessions";
1754
2227
  var READINESS_ORDER = ["needs-setup", "baseline-setup", "needs-improvement", "best-practice-candidate"];
@@ -1767,24 +2240,24 @@ function includesAll(text, values) {
1767
2240
  return values.every((value) => lower.includes(value.toLowerCase()));
1768
2241
  }
1769
2242
  function readDoc(cwd, file) {
1770
- const path = join11(cwd, file);
1771
- return existsSync11(path) ? readFileSync9(path, "utf8") : "";
2243
+ const path = join13(cwd, file);
2244
+ return existsSync12(path) ? readFileSync11(path, "utf8") : "";
1772
2245
  }
1773
2246
  function isPackageRepository(cwd) {
1774
- const packagePath = join11(cwd, "package.json");
1775
- if (!existsSync11(packagePath)) return false;
2247
+ const packagePath = join13(cwd, "package.json");
2248
+ if (!existsSync12(packagePath)) return false;
1776
2249
  try {
1777
- const packageJson = JSON.parse(readFileSync9(packagePath, "utf8"));
1778
- return packageJson.name === "@appsforgood/next-supabase-kit" && existsSync11(join11(cwd, "src", "cli", "index.ts")) && existsSync11(join11(cwd, "templates", "next-supabase")) && existsSync11(join11(cwd, "rosters", "next-supabase-default-council.json"));
2250
+ const packageJson = JSON.parse(readFileSync11(packagePath, "utf8"));
2251
+ return packageJson.name === "@appsforgood/next-supabase-kit" && existsSync12(join13(cwd, "src", "cli", "index.ts")) && existsSync12(join13(cwd, "templates", "next-supabase")) && existsSync12(join13(cwd, "rosters", "next-supabase-default-council.json"));
1779
2252
  } catch {
1780
2253
  return false;
1781
2254
  }
1782
2255
  }
1783
2256
  function readOverrides(cwd) {
1784
- const path = join11(cwd, ".agent-kit", "overrides.json");
1785
- if (!existsSync11(path)) return {};
2257
+ const path = join13(cwd, ".agent-kit", "overrides.json");
2258
+ if (!existsSync12(path)) return {};
1786
2259
  try {
1787
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
2260
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
1788
2261
  const templates = parsed.templates ?? {};
1789
2262
  return Object.fromEntries(
1790
2263
  Object.entries(templates).map(([file, override]) => [
@@ -1797,8 +2270,8 @@ function readOverrides(cwd) {
1797
2270
  }
1798
2271
  }
1799
2272
  function readTemplate(stack, file) {
1800
- const path = join11(findPackageRoot(), "templates", stack, file);
1801
- return existsSync11(path) ? readFileSync9(path, "utf8") : null;
2273
+ const path = join13(findPackageRoot(), "templates", stack, file);
2274
+ return existsSync12(path) ? readFileSync11(path, "utf8") : null;
1802
2275
  }
1803
2276
  function asStringArray(value) {
1804
2277
  if (!Array.isArray(value)) return [];
@@ -1808,8 +2281,8 @@ function isRecord(value) {
1808
2281
  return typeof value === "object" && value !== null;
1809
2282
  }
1810
2283
  function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGENT_ROSTER_TARGET) {
1811
- const rosterPath = join11(cwd, rosterRelativePath);
1812
- if (!existsSync11(rosterPath)) {
2284
+ const rosterPath = join13(cwd, rosterRelativePath);
2285
+ if (!existsSync12(rosterPath)) {
1813
2286
  findings.push({
1814
2287
  level: "fail",
1815
2288
  area: "agents",
@@ -1820,7 +2293,7 @@ function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGEN
1820
2293
  }
1821
2294
  let roster;
1822
2295
  try {
1823
- const parsed = JSON.parse(readFileSync9(rosterPath, "utf8"));
2296
+ const parsed = JSON.parse(readFileSync11(rosterPath, "utf8"));
1824
2297
  if (!isRecord(parsed)) throw new Error("Roster must be a JSON object.");
1825
2298
  const contractResult = AgentRosterContract.safeParse(parsed);
1826
2299
  if (!contractResult.success) {
@@ -2007,15 +2480,15 @@ function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGEN
2007
2480
  }
2008
2481
  }
2009
2482
  function addCouncilSessionRecordFindings(cwd, findings) {
2010
- const sessionsRoot = join11(cwd, COUNCIL_SESSION_DIR);
2011
- if (!existsSync11(sessionsRoot)) return;
2483
+ const sessionsRoot = join13(cwd, COUNCIL_SESSION_DIR);
2484
+ if (!existsSync12(sessionsRoot)) return;
2012
2485
  const sessionFiles = listFilesRecursive(sessionsRoot).filter((file) => file.endsWith(".json") && !/[\\/]/.test(file));
2013
2486
  if (sessionFiles.length === 0) return;
2014
2487
  let invalidCount = 0;
2015
2488
  for (const sessionFile of sessionFiles) {
2016
2489
  const displayPath = `${COUNCIL_SESSION_DIR}/${sessionFile}`;
2017
2490
  try {
2018
- const parsed = JSON.parse(readFileSync9(join11(sessionsRoot, sessionFile), "utf8"));
2491
+ const parsed = JSON.parse(readFileSync11(join13(sessionsRoot, sessionFile), "utf8"));
2019
2492
  const contractResult = CouncilSessionContract.safeParse(parsed);
2020
2493
  if (!contractResult.success) {
2021
2494
  invalidCount += 1;
@@ -2046,8 +2519,8 @@ function addCouncilSessionRecordFindings(cwd, findings) {
2046
2519
  }
2047
2520
  function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/schemas") {
2048
2521
  for (const schemaFile of REQUIRED_SCHEMA_FILES) {
2049
- const schemaPath = join11(cwd, schemaRootRelativePath, schemaFile);
2050
- if (!existsSync11(schemaPath)) {
2522
+ const schemaPath = join13(cwd, schemaRootRelativePath, schemaFile);
2523
+ if (!existsSync12(schemaPath)) {
2051
2524
  findings.push({
2052
2525
  level: "warn",
2053
2526
  area: "agents",
@@ -2057,7 +2530,7 @@ function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/s
2057
2530
  continue;
2058
2531
  }
2059
2532
  try {
2060
- const parsed = JSON.parse(readFileSync9(schemaPath, "utf8"));
2533
+ const parsed = JSON.parse(readFileSync11(schemaPath, "utf8"));
2061
2534
  if (!isRecord(parsed) || typeof parsed.$schema !== "string" || !isRecord(parsed.properties)) {
2062
2535
  throw new Error("Schema file is missing JSON Schema metadata.");
2063
2536
  }
@@ -2076,9 +2549,54 @@ function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/s
2076
2549
  }
2077
2550
  }
2078
2551
  }
2552
+ function addOrchestratorFindings(cwd, findings, relativePath = DEFAULT_ORCHESTRATOR_TARGET) {
2553
+ const path = join13(cwd, relativePath);
2554
+ if (!existsSync12(path)) {
2555
+ findings.push({
2556
+ level: "warn",
2557
+ area: "orchestrator",
2558
+ message: `${relativePath} is missing.`,
2559
+ remediation: "Run agent-kit update to install the disabled-by-default optional runtime policy."
2560
+ });
2561
+ return;
2562
+ }
2563
+ const text = readFileSync11(path, "utf8");
2564
+ if (containsLikelySecret(text)) {
2565
+ findings.push({
2566
+ level: "fail",
2567
+ area: "orchestrator",
2568
+ message: `${relativePath} appears to contain a resolved credential.`,
2569
+ remediation: "Replace credential values with env:NAME or keychain:account references and rotate exposed secrets."
2570
+ });
2571
+ return;
2572
+ }
2573
+ try {
2574
+ const config = JSON.parse(text);
2575
+ if (config.schemaVersion !== 1 || typeof config.enabled !== "boolean") throw new Error("schemaVersion must be 1 and enabled must be boolean");
2576
+ if (!config.providers || typeof config.providers !== "object" || Array.isArray(config.providers)) throw new Error("providers must be an object");
2577
+ if (!config.modelAliases || typeof config.modelAliases !== "object" || Array.isArray(config.modelAliases))
2578
+ throw new Error("modelAliases must be an object");
2579
+ if (config.enabled) {
2580
+ if (typeof config.defaultAlias !== "string" || !config.defaultAlias) throw new Error("enabled runtime requires defaultAlias");
2581
+ if (!Object.hasOwn(config.modelAliases, config.defaultAlias)) throw new Error("enabled runtime defaultAlias is not configured");
2582
+ }
2583
+ findings.push({
2584
+ level: "pass",
2585
+ area: "orchestrator",
2586
+ message: `${relativePath} is valid and ${config.enabled ? "enabled" : "disabled by default"}.`
2587
+ });
2588
+ } catch (error) {
2589
+ findings.push({
2590
+ level: "fail",
2591
+ area: "orchestrator",
2592
+ message: `${relativePath} is invalid: ${error instanceof Error ? error.message : String(error)}.`,
2593
+ remediation: "Restore it from the current template and run agent-kit orchestrate validate before enabling execution."
2594
+ });
2595
+ }
2596
+ }
2079
2597
  function addAgentStudioFindings(cwd, findings) {
2080
- const contextPath = join11(cwd, CONTEXT_JSON);
2081
- if (!existsSync11(contextPath)) {
2598
+ const contextPath = join13(cwd, CONTEXT_JSON);
2599
+ if (!existsSync12(contextPath)) {
2082
2600
  findings.push({
2083
2601
  level: "warn",
2084
2602
  area: "studio",
@@ -2087,7 +2605,7 @@ function addAgentStudioFindings(cwd, findings) {
2087
2605
  });
2088
2606
  } else {
2089
2607
  try {
2090
- const parsed = JSON.parse(readFileSync9(contextPath, "utf8"));
2608
+ const parsed = JSON.parse(readFileSync11(contextPath, "utf8"));
2091
2609
  const result = ProjectContextContract.safeParse(parsed);
2092
2610
  if (!result.success) {
2093
2611
  findings.push({
@@ -2151,8 +2669,8 @@ function addAgentStudioFindings(cwd, findings) {
2151
2669
  });
2152
2670
  }
2153
2671
  }
2154
- const contextMdPath = join11(cwd, CONTEXT_MD);
2155
- if (existsSync11(contextPath) && !existsSync11(contextMdPath)) {
2672
+ const contextMdPath = join13(cwd, CONTEXT_MD);
2673
+ if (existsSync12(contextPath) && !existsSync12(contextMdPath)) {
2156
2674
  findings.push({
2157
2675
  level: "warn",
2158
2676
  area: "studio",
@@ -2161,10 +2679,10 @@ function addAgentStudioFindings(cwd, findings) {
2161
2679
  });
2162
2680
  }
2163
2681
  for (const relativePath of [PROJECT_RULES_JSON, AGENT_RULES_JSON]) {
2164
- const path = join11(cwd, relativePath);
2165
- if (!existsSync11(path)) continue;
2682
+ const path = join13(cwd, relativePath);
2683
+ if (!existsSync12(path)) continue;
2166
2684
  try {
2167
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
2685
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
2168
2686
  const result = CorrectionRulesContract.safeParse(parsed);
2169
2687
  if (!result.success) {
2170
2688
  findings.push({
@@ -2189,9 +2707,9 @@ function addAgentStudioFindings(cwd, findings) {
2189
2707
  });
2190
2708
  }
2191
2709
  }
2192
- const studioExportPath = join11(cwd, STUDIO_EXPORT_HTML);
2193
- if (existsSync11(studioExportPath)) {
2194
- const exportHtml = readFileSync9(studioExportPath, "utf8");
2710
+ const studioExportPath = join13(cwd, STUDIO_EXPORT_HTML);
2711
+ if (existsSync12(studioExportPath)) {
2712
+ const exportHtml = readFileSync11(studioExportPath, "utf8");
2195
2713
  if (containsLikelySecret(exportHtml)) {
2196
2714
  findings.push({
2197
2715
  level: "fail",
@@ -2214,8 +2732,8 @@ function addAgentStudioFindings(cwd, findings) {
2214
2732
  });
2215
2733
  }
2216
2734
  }
2217
- const sessionsRoot = join11(cwd, COUNCIL_SESSION_DIR);
2218
- if (!existsSync11(sessionsRoot)) return;
2735
+ const sessionsRoot = join13(cwd, COUNCIL_SESSION_DIR);
2736
+ if (!existsSync12(sessionsRoot)) return;
2219
2737
  const files = listFilesRecursive(sessionsRoot);
2220
2738
  const studioSessionFiles = files.filter((file) => /[\\/]session\.json$/.test(file));
2221
2739
  for (const sessionFile of studioSessionFiles) {
@@ -2224,10 +2742,10 @@ function addAgentStudioFindings(cwd, findings) {
2224
2742
  const sessionDir2 = sessionFile.replace(/[\\/]session\.json$/, "");
2225
2743
  const normalizedSessionDir = sessionDir2.replace(/\\/g, "/");
2226
2744
  const eventsRelative = `${COUNCIL_SESSION_DIR}/${normalizedSessionDir}/events.jsonl`;
2227
- const sessionDirPath = join11(sessionsRoot, sessionDir2);
2745
+ const sessionDirPath = join13(sessionsRoot, sessionDir2);
2228
2746
  let sessionResult = null;
2229
2747
  try {
2230
- sessionResult = StudioSessionContract.safeParse(JSON.parse(readFileSync9(join11(sessionDirPath, "session.json"), "utf8")));
2748
+ sessionResult = StudioSessionContract.safeParse(JSON.parse(readFileSync11(join13(sessionDirPath, "session.json"), "utf8")));
2231
2749
  if (!sessionResult.success) {
2232
2750
  findings.push({
2233
2751
  level: "fail",
@@ -2246,8 +2764,8 @@ function addAgentStudioFindings(cwd, findings) {
2246
2764
  });
2247
2765
  continue;
2248
2766
  }
2249
- const eventsPath2 = join11(sessionDirPath, "events.jsonl");
2250
- if (!existsSync11(eventsPath2)) {
2767
+ const eventsPath2 = join13(sessionDirPath, "events.jsonl");
2768
+ if (!existsSync12(eventsPath2)) {
2251
2769
  findings.push({
2252
2770
  level: "fail",
2253
2771
  area: "studio",
@@ -2256,7 +2774,7 @@ function addAgentStudioFindings(cwd, findings) {
2256
2774
  });
2257
2775
  continue;
2258
2776
  }
2259
- const eventText = readFileSync9(eventsPath2, "utf8");
2777
+ const eventText = readFileSync11(eventsPath2, "utf8");
2260
2778
  if (containsLikelySecret(eventText)) {
2261
2779
  findings.push({
2262
2780
  level: "fail",
@@ -2291,7 +2809,7 @@ function addAgentStudioFindings(cwd, findings) {
2291
2809
  });
2292
2810
  }
2293
2811
  }
2294
- if (!existsSync11(join11(sessionDirPath, "index.md")) || !existsSync11(join11(sessionDirPath, "transcript.md"))) {
2812
+ if (!existsSync12(join13(sessionDirPath, "index.md")) || !existsSync12(join13(sessionDirPath, "transcript.md"))) {
2295
2813
  findings.push({
2296
2814
  level: "warn",
2297
2815
  area: "studio",
@@ -2299,8 +2817,8 @@ function addAgentStudioFindings(cwd, findings) {
2299
2817
  remediation: "Run agent-kit session render so humans can inspect the current agent transcript and handoffs."
2300
2818
  });
2301
2819
  } else {
2302
- const indexText = readFileSync9(join11(sessionDirPath, "index.md"), "utf8");
2303
- const transcriptText = readFileSync9(join11(sessionDirPath, "transcript.md"), "utf8");
2820
+ const indexText = readFileSync11(join13(sessionDirPath, "index.md"), "utf8");
2821
+ const transcriptText = readFileSync11(join13(sessionDirPath, "transcript.md"), "utf8");
2304
2822
  if (containsLikelySecret(indexText) || containsLikelySecret(transcriptText)) {
2305
2823
  findings.push({
2306
2824
  level: "fail",
@@ -2309,7 +2827,7 @@ function addAgentStudioFindings(cwd, findings) {
2309
2827
  remediation: "Regenerate Markdown after redacting sensitive values from the event log."
2310
2828
  });
2311
2829
  }
2312
- if (statSync(eventsPath2).mtimeMs > statSync(join11(sessionDirPath, "index.md")).mtimeMs) {
2830
+ if (statSync3(eventsPath2).mtimeMs > statSync3(join13(sessionDirPath, "index.md")).mtimeMs) {
2313
2831
  findings.push({
2314
2832
  level: "warn",
2315
2833
  area: "studio",
@@ -2359,8 +2877,8 @@ function addCouncilDocFindings(cwd, findings) {
2359
2877
  }
2360
2878
  function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".agent-kit/assistant-adapters", docsCwd = cwd) {
2361
2879
  const adaptersDoc = readDoc(docsCwd, "ASSISTANT_ADAPTERS.md");
2362
- const adapterRoot = join11(cwd, adapterRootRelativePath);
2363
- if (!existsSync11(adapterRoot)) {
2880
+ const adapterRoot = join13(cwd, adapterRootRelativePath);
2881
+ if (!existsSync12(adapterRoot)) {
2364
2882
  findings.push({
2365
2883
  level: "warn",
2366
2884
  area: "agents",
@@ -2389,7 +2907,7 @@ function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".
2389
2907
  message: "ASSISTANT_ADAPTERS.md maps the council roster to tool-specific instruction surfaces."
2390
2908
  });
2391
2909
  }
2392
- if (assistantAdapterRowIsActive(adaptersDoc, "Cursor") && !existsSync11(join11(cwd, ".cursor/agents/planner.md"))) {
2910
+ if (assistantAdapterRowIsActive(adaptersDoc, "Cursor") && !existsSync12(join13(cwd, ".cursor/agents/planner.md"))) {
2393
2911
  findings.push({
2394
2912
  level: "warn",
2395
2913
  area: "agents",
@@ -2397,7 +2915,7 @@ function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".
2397
2915
  remediation: "Run agent-kit init --activate cursor to generate council subagents from the roster."
2398
2916
  });
2399
2917
  }
2400
- if (assistantAdapterRowIsActive(adaptersDoc, "Codex / AGENTS.md-compatible tools") && !existsSync11(join11(cwd, ".codex/agents/planner.toml"))) {
2918
+ if (assistantAdapterRowIsActive(adaptersDoc, "Codex / AGENTS.md-compatible tools") && !existsSync12(join13(cwd, ".codex/agents/planner.toml"))) {
2401
2919
  findings.push({
2402
2920
  level: "warn",
2403
2921
  area: "agents",
@@ -2445,8 +2963,8 @@ function addModelRoutingFindings(cwd, findings, routingRelativePath = DEFAULT_MO
2445
2963
  message: "MODEL_ROUTING.md documents agent model profiles and IDE enforcement limits."
2446
2964
  });
2447
2965
  }
2448
- const routingPath = join11(cwd, routingRelativePath);
2449
- if (!existsSync11(routingPath)) {
2966
+ const routingPath = join13(cwd, routingRelativePath);
2967
+ if (!existsSync12(routingPath)) {
2450
2968
  findings.push({
2451
2969
  level: "warn",
2452
2970
  area: "models",
@@ -2457,7 +2975,7 @@ function addModelRoutingFindings(cwd, findings, routingRelativePath = DEFAULT_MO
2457
2975
  }
2458
2976
  let routing;
2459
2977
  try {
2460
- routing = JSON.parse(readFileSync9(routingPath, "utf8"));
2978
+ routing = JSON.parse(readFileSync11(routingPath, "utf8"));
2461
2979
  } catch {
2462
2980
  findings.push({
2463
2981
  level: "warn",
@@ -2515,11 +3033,11 @@ function addTemplateHashFindings(cwd, findings) {
2515
3033
  if (!manifest) return;
2516
3034
  const overrides = readOverrides(cwd);
2517
3035
  for (const doc of ROOT_DOCS) {
2518
- const targetPath = join11(cwd, doc);
2519
- if (!existsSync11(targetPath)) continue;
3036
+ const targetPath = join13(cwd, doc);
3037
+ if (!existsSync12(targetPath)) continue;
2520
3038
  const currentTemplate = readTemplate(manifest.stack, doc);
2521
3039
  if (!currentTemplate) continue;
2522
- const targetHash = sha256(readFileSync9(targetPath, "utf8"));
3040
+ const targetHash = sha256(readFileSync11(targetPath, "utf8"));
2523
3041
  const currentTemplateHash = sha256(currentTemplate);
2524
3042
  const installedTemplateHash = manifest.templateHashes?.[doc];
2525
3043
  const override = overrides[doc];
@@ -2811,27 +3329,25 @@ function addMessagingFindings(cwd, findings) {
2811
3329
  });
2812
3330
  }
2813
3331
  }
2814
- function auditProject(cwd) {
3332
+ function auditProject(cwd, options = {}) {
2815
3333
  const findings = [];
2816
3334
  const manifest = readManifest(cwd);
2817
3335
  const packageRepository = isPackageRepository(cwd);
2818
- const packageSourceMode = packageRepository && !manifest;
2819
- const docsCwd = packageSourceMode ? join11(cwd, "templates", "next-supabase") : cwd;
2820
- if (!manifest) {
2821
- if (packageRepository) {
2822
- findings.push({
2823
- level: "pass",
2824
- area: "install",
2825
- message: "Package source repository mode detected; installed-project manifest is not required."
2826
- });
2827
- } else {
2828
- findings.push({
2829
- level: "fail",
2830
- area: "install",
2831
- message: "Project has no .agent-kit/manifest.json.",
2832
- remediation: "Run agent-kit init --stack next-supabase."
2833
- });
2834
- }
3336
+ const packageSourceMode = options.packageSource === true || packageRepository && !manifest;
3337
+ const docsCwd = packageSourceMode ? join13(cwd, "templates", "next-supabase") : cwd;
3338
+ if (packageSourceMode) {
3339
+ findings.push({
3340
+ level: "pass",
3341
+ area: "install",
3342
+ message: "Package source repository mode detected; installed-project manifest is not required."
3343
+ });
3344
+ } else if (!manifest) {
3345
+ findings.push({
3346
+ level: "fail",
3347
+ area: "install",
3348
+ message: "Project has no .agent-kit/manifest.json.",
3349
+ remediation: "Run agent-kit init --stack next-supabase."
3350
+ });
2835
3351
  } else {
2836
3352
  findings.push({
2837
3353
  level: "pass",
@@ -2839,17 +3355,20 @@ function auditProject(cwd) {
2839
3355
  message: `Agent kit installed at version ${manifest.packageVersion}.`
2840
3356
  });
2841
3357
  }
2842
- addTemplateHashFindings(cwd, findings);
3358
+ if (!packageSourceMode) addTemplateHashFindings(cwd, findings);
2843
3359
  addAgentRosterFindings(cwd, findings, packageSourceMode ? "rosters/next-supabase-default-council.json" : DEFAULT_AGENT_ROSTER_TARGET);
2844
3360
  addSchemaFindings(cwd, findings, packageSourceMode ? "schemas" : ".agent-kit/schemas");
2845
- addCouncilSessionRecordFindings(cwd, findings);
2846
- if (!packageRepository || existsSync11(join11(cwd, CONTEXT_JSON)) || existsSync11(join11(cwd, COUNCIL_SESSION_DIR))) {
2847
- addAgentStudioFindings(cwd, findings);
3361
+ addOrchestratorFindings(cwd, findings, packageSourceMode ? "templates/next-supabase/.agent-kit/orchestrator.json" : DEFAULT_ORCHESTRATOR_TARGET);
3362
+ if (!packageSourceMode) {
3363
+ addCouncilSessionRecordFindings(cwd, findings);
3364
+ if (!packageRepository || existsSync12(join13(cwd, CONTEXT_JSON)) || existsSync12(join13(cwd, COUNCIL_SESSION_DIR))) {
3365
+ addAgentStudioFindings(cwd, findings);
3366
+ }
2848
3367
  }
2849
3368
  for (const doc of ROOT_DOCS) {
2850
- const docPath = join11(docsCwd, doc);
3369
+ const docPath = join13(docsCwd, doc);
2851
3370
  const displayPath = packageSourceMode ? `templates/next-supabase/${doc}` : doc;
2852
- if (existsSync11(docPath)) {
3371
+ if (existsSync12(docPath)) {
2853
3372
  findings.push({ level: "pass", area: "docs", message: `${displayPath} exists.` });
2854
3373
  } else {
2855
3374
  findings.push({
@@ -2867,7 +3386,7 @@ function auditProject(cwd) {
2867
3386
  addQualityGateFindings(docsCwd, findings);
2868
3387
  addUpgradeFindings(docsCwd, findings);
2869
3388
  addProjectEvidenceFindings(docsCwd, findings);
2870
- addProjectRealityFindings(cwd, findings, { packageRepository });
3389
+ findings.push(...projectRealityRules.evaluate({ cwd, packageRepository, observedAt: (/* @__PURE__ */ new Date()).toISOString() }));
2871
3390
  const security = readDoc(docsCwd, "SECURITY.md");
2872
3391
  if (!includesAny(security, ["OWASP", "Top 10"])) {
2873
3392
  findings.push({
@@ -2913,132 +3432,14 @@ function auditProject(cwd) {
2913
3432
  }
2914
3433
  return findings;
2915
3434
  }
2916
- function readPackageJson2(cwd) {
2917
- const path = join11(cwd, "package.json");
2918
- if (!existsSync11(path)) return null;
2919
- try {
2920
- return JSON.parse(readFileSync9(path, "utf8"));
2921
- } catch {
2922
- return null;
2923
- }
2924
- }
2925
- function containsLikelySecretForAudit(relativeFile, content) {
2926
- const normalized = relativeFile.replace(/\\/g, "/");
2927
- const testSecretFixture = ["sk", "test", "fake", "secret", "value"].join("_");
2928
- if (normalized.startsWith("tests/") && content.includes(`const fakeSecret = "${testSecretFixture}"`) && content.includes("not.toContain(fakeSecret)")) {
2929
- return containsLikelySecret(content.split(testSecretFixture).join("[TEST_SECRET_FIXTURE]"));
2930
- }
2931
- return containsLikelySecret(content);
2932
- }
2933
- function addProjectRealityFindings(cwd, findings, options = {}) {
2934
- const migrationsDir = join11(cwd, "supabase", "migrations");
2935
- if (existsSync11(migrationsDir)) {
2936
- const sqlFiles = listFilesRecursive(migrationsDir).filter((file) => file.endsWith(".sql"));
2937
- if (sqlFiles.length === 0) {
2938
- findings.push({
2939
- level: "warn",
2940
- area: "project-reality",
2941
- message: "supabase/migrations exists but contains no SQL migration files.",
2942
- remediation: "Add versioned SQL migrations or remove the empty migrations directory if Supabase is not in use."
2943
- });
2944
- } else {
2945
- const rlsFiles = sqlFiles.filter((file) => {
2946
- const content = readFileSync9(join11(migrationsDir, file), "utf8");
2947
- return /enable\s+row\s+level\s+security/i.test(content);
2948
- });
2949
- if (rlsFiles.length === 0) {
2950
- findings.push({
2951
- level: "fail",
2952
- area: "project-reality",
2953
- message: "No Supabase migration enables row level security.",
2954
- remediation: "Add `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` (or equivalent) in supabase/migrations before shipping user data."
2955
- });
2956
- } else {
2957
- findings.push({
2958
- level: "pass",
2959
- area: "project-reality",
2960
- message: `Supabase migrations enable RLS in ${rlsFiles.length} file(s).`
2961
- });
2962
- }
2963
- }
2964
- }
2965
- const packageJson = readPackageJson2(cwd);
2966
- if (!packageJson) {
2967
- findings.push({
2968
- level: "warn",
2969
- area: "project-reality",
2970
- message: "No package.json found to verify test scripts.",
2971
- remediation: "Add package.json with test, lint, and build scripts appropriate to the stack."
2972
- });
2973
- } else {
2974
- const scripts = packageJson.scripts ?? {};
2975
- const testScript = scripts.test ?? scripts["test:unit"] ?? scripts["test:ci"];
2976
- if (!testScript) {
2977
- findings.push({
2978
- level: "warn",
2979
- area: "project-reality",
2980
- message: "package.json has no test script (test, test:unit, or test:ci).",
2981
- remediation: "Add a test script and document it in TESTING.md."
2982
- });
2983
- } else {
2984
- findings.push({
2985
- level: "pass",
2986
- area: "project-reality",
2987
- message: "package.json defines a test script."
2988
- });
2989
- }
2990
- }
2991
- const trackedSourceFiles = listFilesRecursive(cwd).filter((file) => {
2992
- if (file.includes("node_modules/") || file.includes(".agent-kit/")) return false;
2993
- return /\.(ts|tsx|js|jsx|env|json)$/.test(file);
2994
- });
2995
- const secretHits = trackedSourceFiles.map((file) => {
2996
- const content = readFileSync9(join11(cwd, file), "utf8");
2997
- return containsLikelySecretForAudit(file, content) ? file : null;
2998
- }).filter((file) => file !== null).slice(0, 5);
2999
- if (secretHits.length > 0) {
3000
- findings.push({
3001
- level: "fail",
3002
- area: "project-reality",
3003
- message: `Possible committed secret patterns detected in: ${secretHits.join(", ")}.`,
3004
- remediation: "Remove secrets from tracked files, rotate exposed credentials, and use environment variables."
3005
- });
3006
- } else if (trackedSourceFiles.length > 0) {
3007
- findings.push({
3008
- level: "pass",
3009
- area: "project-reality",
3010
- message: "No obvious committed secret patterns detected in tracked source files."
3011
- });
3012
- }
3013
- if (options.packageRepository) {
3014
- findings.push({
3015
- level: "pass",
3016
- area: "project-reality",
3017
- message: "Package source repository mode does not require installed-project context files."
3018
- });
3019
- } else if (!existsSync11(join11(cwd, CONTEXT_JSON))) {
3020
- findings.push({
3021
- level: "warn",
3022
- area: "project-reality",
3023
- message: ".agent-kit/project-context.json is missing.",
3024
- remediation: "Run agent-kit init or agent-kit context init to create project context."
3025
- });
3026
- } else {
3027
- findings.push({
3028
- level: "pass",
3029
- area: "project-reality",
3030
- message: ".agent-kit/project-context.json exists."
3031
- });
3032
- }
3033
- }
3034
- function createReadiness(findings, summary2) {
3035
- const nextActions = findings.filter((finding) => finding.level === "fail" || finding.level === "warn").map((finding) => finding.remediation ?? finding.message).filter((value, index, values) => values.indexOf(value) === index).slice(0, 5);
3036
- if (summary2.fail > 0) {
3037
- return {
3038
- level: "needs-setup",
3039
- summary: "Required setup or contract checks are failing.",
3040
- nextActions
3041
- };
3435
+ function createAuditReadiness(findings, summary2) {
3436
+ const nextActions = findings.filter((finding) => finding.level === "fail" || finding.level === "warn").map((finding) => finding.remediation ?? finding.message).filter((value, index, values) => values.indexOf(value) === index).slice(0, 5);
3437
+ if (summary2.fail > 0) {
3438
+ return {
3439
+ level: "needs-setup",
3440
+ summary: "Required setup or contract checks are failing.",
3441
+ nextActions
3442
+ };
3042
3443
  }
3043
3444
  if (findings.some((finding) => finding.level === "warn" && finding.area === "evidence")) {
3044
3445
  return {
@@ -3060,11 +3461,16 @@ function createReadiness(findings, summary2) {
3060
3461
  nextActions
3061
3462
  };
3062
3463
  }
3063
- function createAuditReport(cwd) {
3064
- const findings = auditProject(cwd);
3464
+ function createAuditReport(cwd, options = {}) {
3465
+ const findings = auditProject(cwd, options).map((finding) => ({
3466
+ level: finding.level,
3467
+ area: finding.area,
3468
+ message: finding.message,
3469
+ ...finding.remediation ? { remediation: finding.remediation } : {}
3470
+ }));
3065
3471
  const summary2 = { pass: 0, warn: 0, fail: 0 };
3066
3472
  for (const finding of findings) summary2[finding.level] += 1;
3067
- return { summary: summary2, readiness: createReadiness(findings, summary2), findings };
3473
+ return { summary: summary2, readiness: createAuditReadiness(findings, summary2), findings };
3068
3474
  }
3069
3475
 
3070
3476
  // src/install/adapter-validate.ts
@@ -3103,7 +3509,7 @@ function report(target, findings) {
3103
3509
  }
3104
3510
  function readJson(path) {
3105
3511
  try {
3106
- return JSON.parse(readFileSync10(path, "utf8"));
3512
+ return JSON.parse(readFileSync12(path, "utf8"));
3107
3513
  } catch {
3108
3514
  return null;
3109
3515
  }
@@ -3117,24 +3523,24 @@ function isSafeRelativePath(path) {
3117
3523
  return !normalized.startsWith("/") && !normalized.startsWith("../") && !normalized.includes("/../");
3118
3524
  }
3119
3525
  function findAntigravityLayout(cwd) {
3120
- const sourcePlugin = join12(cwd, "antigravity", "plugin.json");
3121
- if (existsSync12(sourcePlugin)) {
3526
+ const sourcePlugin = join14(cwd, "antigravity", "plugin.json");
3527
+ if (existsSync13(sourcePlugin)) {
3122
3528
  return {
3123
3529
  mode: "source",
3124
- pluginRoot: join12(cwd, "antigravity"),
3125
- commandsRoot: join12(cwd, ANTIGRAVITY_COMMANDS_SOURCE_DIR),
3126
- runtimeSkillsRoot: join12(cwd, RUNTIME_SKILLS_SOURCE_DIR),
3127
- adapterDocPath: join12(cwd, "assistant-adapters", "antigravity.md")
3530
+ pluginRoot: join14(cwd, "antigravity"),
3531
+ commandsRoot: join14(cwd, ANTIGRAVITY_COMMANDS_SOURCE_DIR),
3532
+ runtimeSkillsRoot: join14(cwd, RUNTIME_SKILLS_SOURCE_DIR),
3533
+ adapterDocPath: join14(cwd, "assistant-adapters", "antigravity.md")
3128
3534
  };
3129
3535
  }
3130
- const installedPlugin = join12(cwd, ".antigravity", "agent-kit", "plugin.json");
3131
- if (existsSync12(installedPlugin)) {
3536
+ const installedPlugin = join14(cwd, ".antigravity", "agent-kit", "plugin.json");
3537
+ if (existsSync13(installedPlugin)) {
3132
3538
  return {
3133
3539
  mode: "installed",
3134
- pluginRoot: join12(cwd, ".antigravity", "agent-kit"),
3135
- commandsRoot: join12(cwd, ANTIGRAVITY_COMMANDS_TARGET_DIR),
3136
- runtimeSkillsRoot: join12(cwd, ANTIGRAVITY_RUNTIME_SKILLS_TARGET_DIR),
3137
- adapterDocPath: join12(cwd, ".antigravity", "agent-kit", "README.md")
3540
+ pluginRoot: join14(cwd, ".antigravity", "agent-kit"),
3541
+ commandsRoot: join14(cwd, ANTIGRAVITY_COMMANDS_TARGET_DIR),
3542
+ runtimeSkillsRoot: join14(cwd, ANTIGRAVITY_RUNTIME_SKILLS_TARGET_DIR),
3543
+ adapterDocPath: join14(cwd, ".antigravity", "agent-kit", "README.md")
3138
3544
  };
3139
3545
  }
3140
3546
  return null;
@@ -3158,8 +3564,8 @@ function validateAntigravityCommands(layout, findings) {
3158
3564
  const commandNames = /* @__PURE__ */ new Set();
3159
3565
  for (const command of REQUIRED_COMMANDS) {
3160
3566
  const relativePath = `${command}.toml`;
3161
- const path = join12(layout.commandsRoot, relativePath);
3162
- if (!existsSync12(path)) {
3567
+ const path = join14(layout.commandsRoot, relativePath);
3568
+ if (!existsSync13(path)) {
3163
3569
  findings.push({
3164
3570
  level: "fail",
3165
3571
  area: "commands",
@@ -3168,7 +3574,7 @@ function validateAntigravityCommands(layout, findings) {
3168
3574
  });
3169
3575
  continue;
3170
3576
  }
3171
- const text = readFileSync10(path, "utf8");
3577
+ const text = readFileSync12(path, "utf8");
3172
3578
  addSecretFinding(relativePath, text, findings);
3173
3579
  const name = commandField(text, "name");
3174
3580
  const description = commandField(text, "description");
@@ -3225,7 +3631,7 @@ function validateAntigravityCommands(layout, findings) {
3225
3631
  }
3226
3632
  }
3227
3633
  function validateAntigravityPlugin(layout, findings) {
3228
- const pluginPath = join12(layout.pluginRoot, "plugin.json");
3634
+ const pluginPath = join14(layout.pluginRoot, "plugin.json");
3229
3635
  const plugin = readJson(pluginPath);
3230
3636
  if (!plugin || !isRecord2(plugin)) {
3231
3637
  findings.push({
@@ -3266,8 +3672,8 @@ function validateAntigravityPlugin(layout, findings) {
3266
3672
  });
3267
3673
  continue;
3268
3674
  }
3269
- const resolved = join12(layout.pluginRoot, path);
3270
- if (!existsSync12(resolved)) {
3675
+ const resolved = join14(layout.pluginRoot, path);
3676
+ if (!existsSync13(resolved)) {
3271
3677
  findings.push({
3272
3678
  level: "fail",
3273
3679
  area: "manifest",
@@ -3276,7 +3682,7 @@ function validateAntigravityPlugin(layout, findings) {
3276
3682
  });
3277
3683
  }
3278
3684
  }
3279
- const pluginText = readFileSync10(pluginPath, "utf8");
3685
+ const pluginText = readFileSync12(pluginPath, "utf8");
3280
3686
  addSecretFinding("plugin.json", pluginText, findings);
3281
3687
  if (Array.isArray(plugin.sourceOfTruth) && plugin.sourceOfTruth.includes("AGENTS.md") && plugin.sourceOfTruth.includes(".agent-kit/agent-roster.json")) {
3282
3688
  findings.push({
@@ -3294,13 +3700,13 @@ function validateAntigravityPlugin(layout, findings) {
3294
3700
  }
3295
3701
  }
3296
3702
  function validateRuntimeSkills(cwd, layout, findings) {
3297
- const canonicalSkillsRoot = existsSync12(join12(cwd, "skills")) ? join12(cwd, "skills") : join12(cwd, ".agent-kit", "skills");
3703
+ const canonicalSkillsRoot = existsSync13(join14(cwd, "skills")) ? join14(cwd, "skills") : join14(cwd, ".agent-kit", "skills");
3298
3704
  const canonicalSkillNames = listFilesRecursive(canonicalSkillsRoot).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, ""));
3299
3705
  const runtimeSkillFiles = listFilesRecursive(layout.runtimeSkillsRoot).filter((file) => file.endsWith("/SKILL.md") || file === "SKILL.md");
3300
3706
  const runtimeSkillNames = runtimeSkillFiles.map((file) => file.split(/[\\/]/)[0]).filter((value) => typeof value === "string" && value.length > 0).filter((value, index, values) => values.indexOf(value) === index);
3301
3707
  for (const skillName of canonicalSkillNames) {
3302
- const runtimePath = join12(layout.runtimeSkillsRoot, skillName, "SKILL.md");
3303
- if (!existsSync12(runtimePath)) {
3708
+ const runtimePath = join14(layout.runtimeSkillsRoot, skillName, "SKILL.md");
3709
+ if (!existsSync13(runtimePath)) {
3304
3710
  findings.push({
3305
3711
  level: "fail",
3306
3712
  area: "runtime-skills",
@@ -3309,7 +3715,7 @@ function validateRuntimeSkills(cwd, layout, findings) {
3309
3715
  });
3310
3716
  continue;
3311
3717
  }
3312
- const text = readFileSync10(runtimePath, "utf8");
3718
+ const text = readFileSync12(runtimePath, "utf8");
3313
3719
  addSecretFinding(`${skillName}/SKILL.md`, text, findings);
3314
3720
  if (!/^---\nname: .+\ndescription: .+\n---/m.test(text)) {
3315
3721
  findings.push({
@@ -3337,7 +3743,7 @@ function validateRuntimeSkills(cwd, layout, findings) {
3337
3743
  remediation: "Add canonical skills or remove orphan runtime wrappers."
3338
3744
  });
3339
3745
  }
3340
- if (canonicalSkillNames.length > 0 && canonicalSkillNames.every((skillName) => existsSync12(join12(layout.runtimeSkillsRoot, skillName, "SKILL.md")))) {
3746
+ if (canonicalSkillNames.length > 0 && canonicalSkillNames.every((skillName) => existsSync13(join14(layout.runtimeSkillsRoot, skillName, "SKILL.md")))) {
3341
3747
  findings.push({
3342
3748
  level: "pass",
3343
3749
  area: "runtime-skills",
@@ -3358,7 +3764,7 @@ function validateAntigravity(cwd) {
3358
3764
  }
3359
3765
  ]);
3360
3766
  }
3361
- const adapterDoc = existsSync12(layout.adapterDocPath) ? readFileSync10(layout.adapterDocPath, "utf8") : "";
3767
+ const adapterDoc = existsSync13(layout.adapterDocPath) ? readFileSync12(layout.adapterDocPath, "utf8") : "";
3362
3768
  if (!adapterDoc) {
3363
3769
  findings.push({
3364
3770
  level: "fail",
@@ -3387,7 +3793,7 @@ function validateAntigravity(cwd) {
3387
3793
  validateAntigravityCommands(layout, findings);
3388
3794
  validateRuntimeSkills(cwd, layout, findings);
3389
3795
  if (layout.mode === "source") {
3390
- const packageJson = readJson(join12(cwd, "package.json"));
3796
+ const packageJson = readJson(join14(cwd, "package.json"));
3391
3797
  const files = isRecord2(packageJson) && Array.isArray(packageJson.files) ? packageJson.files : [];
3392
3798
  for (const requiredFile of ["antigravity", "runtime-skills", "assistant-adapters"]) {
3393
3799
  if (!files.includes(requiredFile)) {
@@ -3411,7 +3817,7 @@ function validateAntigravity(cwd) {
3411
3817
  }
3412
3818
  function validateBasicAdapter(cwd, target) {
3413
3819
  const findings = [];
3414
- const isPackageSource = existsSync12(join12(cwd, "package.json")) && existsSync12(join12(cwd, "src")) && existsSync12(join12(cwd, "templates"));
3820
+ const isPackageSource = existsSync13(join14(cwd, "package.json")) && existsSync13(join14(cwd, "src")) && existsSync13(join14(cwd, "templates"));
3415
3821
  if (isPackageSource) {
3416
3822
  const sourcePaths = {
3417
3823
  cursor: [
@@ -3424,8 +3830,8 @@ function validateBasicAdapter(cwd, target) {
3424
3830
  copilot: ["assistant-adapters/github-copilot-instructions.md", "assistant-adapters/github-next-supabase.instructions.md"]
3425
3831
  };
3426
3832
  for (const relativePath of sourcePaths[target]) {
3427
- const path = join12(cwd, relativePath);
3428
- if (!existsSync12(path)) {
3833
+ const path = join14(cwd, relativePath);
3834
+ if (!existsSync13(path)) {
3429
3835
  findings.push({
3430
3836
  level: "fail",
3431
3837
  area: "adapter",
@@ -3434,7 +3840,7 @@ function validateBasicAdapter(cwd, target) {
3434
3840
  });
3435
3841
  continue;
3436
3842
  }
3437
- const text = readFileSync10(path, "utf8");
3843
+ const text = readFileSync12(path, "utf8");
3438
3844
  addSecretFinding(relativePath, text, findings);
3439
3845
  if (!text.includes("AGENTS.md") && !text.includes("MODEL_ROUTING.md")) {
3440
3846
  findings.push({
@@ -3458,8 +3864,8 @@ function validateBasicAdapter(cwd, target) {
3458
3864
  return report(target, findings);
3459
3865
  }
3460
3866
  function readAssistantAdaptersDoc(cwd) {
3461
- const path = join12(cwd, "ASSISTANT_ADAPTERS.md");
3462
- return existsSync12(path) ? readFileSync10(path, "utf8") : "";
3867
+ const path = join14(cwd, "ASSISTANT_ADAPTERS.md");
3868
+ return existsSync13(path) ? readFileSync12(path, "utf8") : "";
3463
3869
  }
3464
3870
  function adaptersRowIsActive(doc, toolLabel) {
3465
3871
  return assistantAdapterRowIsActive(doc, toolLabel);
@@ -3468,8 +3874,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3468
3874
  const findings = [];
3469
3875
  const adaptersDoc = readAssistantAdaptersDoc(cwd);
3470
3876
  if (target === "cursor") {
3471
- const rulesPath = join12(cwd, ".cursor/rules/cursor-agent-kit.mdc");
3472
- if (!existsSync12(rulesPath)) {
3877
+ const rulesPath = join14(cwd, ".cursor/rules/cursor-agent-kit.mdc");
3878
+ if (!existsSync13(rulesPath)) {
3473
3879
  findings.push({
3474
3880
  level: "fail",
3475
3881
  area: "adapter",
@@ -3477,10 +3883,10 @@ function validateInstalledIdeAdapter(cwd, target) {
3477
3883
  remediation: "Run agent-kit init or agent-kit init --activate cursor."
3478
3884
  });
3479
3885
  } else {
3480
- addSecretFinding(".cursor/rules/cursor-agent-kit.mdc", readFileSync10(rulesPath, "utf8"), findings);
3886
+ addSecretFinding(".cursor/rules/cursor-agent-kit.mdc", readFileSync12(rulesPath, "utf8"), findings);
3481
3887
  }
3482
- const plannerAgent = join12(cwd, ".cursor/agents/planner.md");
3483
- if (existsSync12(plannerAgent)) {
3888
+ const plannerAgent = join14(cwd, ".cursor/agents/planner.md");
3889
+ if (existsSync13(plannerAgent)) {
3484
3890
  findings.push({
3485
3891
  level: "pass",
3486
3892
  area: "adapter",
@@ -3494,8 +3900,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3494
3900
  remediation: "Run agent-kit init --activate cursor to generate council subagents from the roster."
3495
3901
  });
3496
3902
  }
3497
- const skillSample = join12(cwd, ".cursor/skills/planning-council/SKILL.md");
3498
- if (existsSync12(skillSample)) {
3903
+ const skillSample = join14(cwd, ".cursor/skills/planning-council/SKILL.md");
3904
+ if (existsSync13(skillSample)) {
3499
3905
  findings.push({
3500
3906
  level: "pass",
3501
3907
  area: "adapter",
@@ -3504,8 +3910,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3504
3910
  }
3505
3911
  }
3506
3912
  if (target === "claude") {
3507
- const plannerAgent = join12(cwd, ".claude/agents/planner.md");
3508
- if (!existsSync12(plannerAgent)) {
3913
+ const plannerAgent = join14(cwd, ".claude/agents/planner.md");
3914
+ if (!existsSync13(plannerAgent)) {
3509
3915
  findings.push({
3510
3916
  level: "fail",
3511
3917
  area: "adapter",
@@ -3515,8 +3921,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3515
3921
  }
3516
3922
  }
3517
3923
  if (target === "codex") {
3518
- const configPath = join12(cwd, ".codex/config.toml");
3519
- if (!existsSync12(configPath)) {
3924
+ const configPath = join14(cwd, ".codex/config.toml");
3925
+ if (!existsSync13(configPath)) {
3520
3926
  findings.push({
3521
3927
  level: "fail",
3522
3928
  area: "adapter",
@@ -3524,8 +3930,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3524
3930
  remediation: "Run agent-kit init --activate codex."
3525
3931
  });
3526
3932
  }
3527
- const plannerAgent = join12(cwd, ".codex/agents/planner.toml");
3528
- if (existsSync12(plannerAgent)) {
3933
+ const plannerAgent = join14(cwd, ".codex/agents/planner.toml");
3934
+ if (existsSync13(plannerAgent)) {
3529
3935
  findings.push({
3530
3936
  level: "pass",
3531
3937
  area: "adapter",
@@ -3541,8 +3947,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3541
3947
  }
3542
3948
  }
3543
3949
  if (target === "copilot") {
3544
- const instructions = join12(cwd, ".github/copilot-instructions.md");
3545
- if (!existsSync12(instructions)) {
3950
+ const instructions = join14(cwd, ".github/copilot-instructions.md");
3951
+ if (!existsSync13(instructions)) {
3546
3952
  findings.push({
3547
3953
  level: "fail",
3548
3954
  area: "adapter",
@@ -3566,7 +3972,7 @@ function validateAdapter(cwd, target = "antigravity") {
3566
3972
  }
3567
3973
  function validatePackage(cwd) {
3568
3974
  const findings = [];
3569
- const sourceMode = existsSync12(join12(cwd, "package.json")) && existsSync12(join12(cwd, "src")) && existsSync12(join12(cwd, "templates"));
3975
+ const sourceMode = existsSync13(join14(cwd, "package.json")) && existsSync13(join14(cwd, "src")) && existsSync13(join14(cwd, "templates"));
3570
3976
  if (!sourceMode) {
3571
3977
  return report("package", [
3572
3978
  {
@@ -3578,9 +3984,40 @@ function validatePackage(cwd) {
3578
3984
  ]);
3579
3985
  }
3580
3986
  findings.push(...validateAntigravity(cwd).findings);
3987
+ const runtimeRequired = [
3988
+ "packages/runtime/package.json",
3989
+ "packages/runtime/README.md",
3990
+ "packages/runtime/CHANGELOG.md",
3991
+ "packages/runtime/LICENSE",
3992
+ "packages/runtime/src/index.ts"
3993
+ ];
3994
+ for (const target of runtimeRequired) {
3995
+ if (!existsSync13(join14(cwd, target))) {
3996
+ findings.push({ level: "fail", area: "runtime-package", message: `${target} is missing.` });
3997
+ }
3998
+ }
3999
+ const runtimeManifest = readJson(join14(cwd, "packages", "runtime", "package.json"));
4000
+ if (!isRecord2(runtimeManifest) || runtimeManifest.name !== "@appsforgood/agent-kit-runtime") {
4001
+ findings.push({ level: "fail", area: "runtime-package", message: "Runtime package name is missing or invalid." });
4002
+ } else {
4003
+ const publishConfig = isRecord2(runtimeManifest.publishConfig) ? runtimeManifest.publishConfig : {};
4004
+ const files = Array.isArray(runtimeManifest.files) ? runtimeManifest.files : [];
4005
+ if (runtimeManifest.license !== "MIT") {
4006
+ findings.push({ level: "fail", area: "runtime-package", message: "Runtime package must declare the MIT license." });
4007
+ }
4008
+ if (publishConfig.access !== "public" || publishConfig.provenance !== true) {
4009
+ findings.push({ level: "fail", area: "runtime-package", message: "Runtime package must require public provenance publishing." });
4010
+ }
4011
+ for (const required of ["dist", "README.md", "CHANGELOG.md", "LICENSE"]) {
4012
+ if (!files.includes(required)) findings.push({ level: "fail", area: "runtime-package", message: `Runtime package files omits ${required}.` });
4013
+ }
4014
+ if (findings.every((finding) => finding.area !== "runtime-package" || finding.level !== "fail")) {
4015
+ findings.push({ level: "pass", area: "runtime-package", message: "Runtime package metadata and public artifacts are complete." });
4016
+ }
4017
+ }
3581
4018
  for (const doc of ["README.md", "DOCS.md", "SPEC.md", "DECISIONS.md", "QUALITY_GATES.md", "TESTING.md", "UPGRADE.md"]) {
3582
- const path = join12(cwd, doc);
3583
- const text = existsSync12(path) ? readFileSync10(path, "utf8") : "";
4019
+ const path = join14(cwd, doc);
4020
+ const text = existsSync13(path) ? readFileSync12(path, "utf8") : "";
3584
4021
  const lower = text.toLowerCase();
3585
4022
  if (!lower.includes("antigravity") && !lower.includes("runtime command") && !lower.includes("runtime adapter")) {
3586
4023
  findings.push({
@@ -3597,7 +4034,7 @@ function validatePackage(cwd) {
3597
4034
  "examples/next-supabase-installed/.agent-kit/manifest.json",
3598
4035
  "examples/next-supabase-installed/audit-output.json"
3599
4036
  ]) {
3600
- if (!existsSync12(join12(cwd, examplePath))) {
4037
+ if (!existsSync13(join14(cwd, examplePath))) {
3601
4038
  findings.push({
3602
4039
  level: "fail",
3603
4040
  area: "examples",
@@ -3606,7 +4043,7 @@ function validatePackage(cwd) {
3606
4043
  });
3607
4044
  }
3608
4045
  }
3609
- const auditReport = createAuditReport(cwd);
4046
+ const auditReport = createAuditReport(cwd, { packageSource: true });
3610
4047
  if (auditReport.summary.fail > 0) {
3611
4048
  findings.push({
3612
4049
  level: "fail",
@@ -3631,118 +4068,227 @@ function validatePackage(cwd) {
3631
4068
  return report("package", findings);
3632
4069
  }
3633
4070
 
3634
- // src/install/diff.ts
3635
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
3636
- import { join as join13 } from "path";
3637
- function statusForTextFile(target, template) {
3638
- if (!existsSync13(target)) return "missing";
3639
- const targetHash = sha256(readFileSync11(target, "utf8"));
3640
- const templateHash = sha256(readFileSync11(template, "utf8"));
3641
- return targetHash === templateHash ? "unchanged" : "changed";
4071
+ // src/install/audit-v2.ts
4072
+ import { createHash as createHash2 } from "crypto";
4073
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
4074
+ import { join as join15 } from "path";
4075
+ function fallbackRuleId(finding) {
4076
+ const area = finding.area.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "general";
4077
+ const digest = createHash2("sha256").update(`${finding.area}\0${finding.message}`).digest("hex").slice(0, 12);
4078
+ return `legacy.${area}.${digest}`;
4079
+ }
4080
+ function loadSuppressions(cwd) {
4081
+ const path = join15(cwd, ".agent-kit", "overrides.json");
4082
+ if (!existsSync14(path)) return {};
4083
+ try {
4084
+ const parsed = JSON.parse(readFileSync13(path, "utf8"));
4085
+ return parsed.auditRules ?? {};
4086
+ } catch {
4087
+ return {};
4088
+ }
3642
4089
  }
3643
- function diffProject(cwd, stack = "next-supabase") {
3644
- const packageRoot = findPackageRoot();
3645
- const templateRoot = join13(packageRoot, "templates", stack);
3646
- const libraryFolders = {
3647
- missing: [],
3648
- present: [],
3649
- willRefresh: [...LIBRARY_FOLDERS]
4090
+ function validSuppression(suppression, now) {
4091
+ if (!suppression?.reason?.trim() || !suppression.owner?.trim() || !suppression.reviewedAt?.trim()) return false;
4092
+ if (suppression.expiresAt && Date.parse(suppression.expiresAt) <= now) return false;
4093
+ return true;
4094
+ }
4095
+ function createAuditReportV2(cwd) {
4096
+ const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
4097
+ const suppressions = loadSuppressions(cwd);
4098
+ const findings = auditProject(cwd).map((finding) => {
4099
+ const ruleId = finding.ruleId ?? fallbackRuleId(finding);
4100
+ const suppression = suppressions[ruleId];
4101
+ const suppressed = finding.level !== "pass" && validSuppression(suppression, Date.now());
4102
+ return {
4103
+ ...finding,
4104
+ ruleId,
4105
+ ruleVersion: finding.ruleVersion ?? "1.0.0",
4106
+ confidence: finding.confidence ?? "low",
4107
+ evidence: finding.evidence ?? [],
4108
+ ...suppressed ? { suppressed: true, suppressionReason: suppression.reason } : {}
4109
+ };
4110
+ });
4111
+ const activeFindings = findings.filter((finding) => !finding.suppressed);
4112
+ const summary2 = { pass: 0, warn: 0, fail: 0, suppressed: findings.length - activeFindings.length };
4113
+ for (const finding of activeFindings) summary2[finding.level] += 1;
4114
+ return {
4115
+ schemaVersion: 2,
4116
+ generatedAt,
4117
+ tool: { name: "agent-kit", version: PACKAGE_VERSION },
4118
+ root: ".",
4119
+ summary: summary2,
4120
+ readiness: createAuditReadiness(activeFindings, summary2),
4121
+ findings
3650
4122
  };
3651
- const result = {
3652
- missing: [],
3653
- unchanged: [],
3654
- changed: [],
3655
- agentRoster: "missing",
3656
- modelRouting: "missing",
3657
- libraryFolders,
3658
- preview: {
3659
- wouldCreate: [],
3660
- wouldWriteConflicts: [],
3661
- wouldRefreshLibraryFolders: [...LIBRARY_FOLDERS],
3662
- wouldCreateAgentRoster: false,
3663
- wouldWriteAgentRosterConflict: false,
3664
- wouldCreateModelRouting: false,
3665
- wouldWriteModelRoutingConflict: false
3666
- }
4123
+ }
4124
+ function auditReportToSarif(report2) {
4125
+ const rules = [...new Map(report2.findings.map((finding) => [finding.ruleId, finding])).values()].map((finding) => ({
4126
+ id: finding.ruleId,
4127
+ name: finding.ruleId,
4128
+ shortDescription: { text: finding.message },
4129
+ ...finding.helpUri ? { helpUri: finding.helpUri } : {},
4130
+ properties: { area: finding.area, ruleVersion: finding.ruleVersion, confidence: finding.confidence, fixable: Boolean(finding.fixable) }
4131
+ }));
4132
+ const results = report2.findings.filter((finding) => finding.level !== "pass").map((finding) => {
4133
+ const evidence = finding.evidence.find((item) => item.path);
4134
+ const locations = evidence?.path ? [
4135
+ {
4136
+ physicalLocation: {
4137
+ artifactLocation: { uri: evidence.path.replace(/\\/g, "/") },
4138
+ ...evidence.line ? { region: { startLine: evidence.line, ...evidence.column ? { startColumn: evidence.column } : {} } } : {}
4139
+ }
4140
+ }
4141
+ ] : [];
4142
+ return {
4143
+ ruleId: finding.ruleId,
4144
+ level: finding.level === "fail" ? "error" : "warning",
4145
+ message: { text: finding.message },
4146
+ ...locations.length > 0 ? { locations } : {},
4147
+ ...finding.suppressed ? { suppressions: [{ kind: "external", justification: finding.suppressionReason }] } : {},
4148
+ properties: { area: finding.area, confidence: finding.confidence, remediation: finding.remediation ?? "" }
4149
+ };
4150
+ });
4151
+ return {
4152
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
4153
+ version: "2.1.0",
4154
+ runs: [
4155
+ {
4156
+ tool: {
4157
+ driver: {
4158
+ name: "agent-kit",
4159
+ version: report2.tool.version,
4160
+ informationUri: "https://github.com/lukey662/agentsandskills",
4161
+ rules
4162
+ }
4163
+ },
4164
+ results
4165
+ }
4166
+ ]
3667
4167
  };
3668
- for (const doc of ROOT_DOCS) {
3669
- const target = join13(cwd, doc);
3670
- const template = join13(templateRoot, doc);
3671
- const status = statusForTextFile(target, template);
3672
- if (status === "missing") {
3673
- result.missing.push(doc);
3674
- result.preview.wouldCreate.push(doc);
3675
- continue;
3676
- }
3677
- if (status === "unchanged") result.unchanged.push(doc);
3678
- else {
3679
- result.changed.push(doc);
3680
- result.preview.wouldWriteConflicts.push(doc);
3681
- }
3682
- }
3683
- result.agentRoster = statusForTextFile(join13(cwd, DEFAULT_AGENT_ROSTER_TARGET), join13(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE));
3684
- if (result.agentRoster === "missing") {
3685
- result.preview.wouldCreate.push(DEFAULT_AGENT_ROSTER_TARGET);
3686
- result.preview.wouldCreateAgentRoster = true;
3687
- }
3688
- if (result.agentRoster === "changed") {
3689
- result.preview.wouldWriteConflicts.push(DEFAULT_AGENT_ROSTER_TARGET);
3690
- result.preview.wouldWriteAgentRosterConflict = true;
3691
- }
3692
- result.modelRouting = statusForTextFile(join13(cwd, DEFAULT_MODEL_ROUTING_TARGET), join13(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE));
3693
- if (result.modelRouting === "missing") {
3694
- result.preview.wouldCreate.push(DEFAULT_MODEL_ROUTING_TARGET);
3695
- result.preview.wouldCreateModelRouting = true;
3696
- }
3697
- if (result.modelRouting === "changed") {
3698
- result.preview.wouldWriteConflicts.push(DEFAULT_MODEL_ROUTING_TARGET);
3699
- result.preview.wouldWriteModelRoutingConflict = true;
3700
- }
3701
- for (const folder of LIBRARY_FOLDERS) {
3702
- const target = join13(cwd, ".agent-kit", folder);
3703
- if (existsSync13(target)) libraryFolders.present.push(folder);
3704
- else libraryFolders.missing.push(folder);
3705
- }
3706
- return result;
3707
4168
  }
3708
4169
 
3709
- // src/install/update.ts
3710
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
3711
- import { join as join14, relative as relative2 } from "path";
3712
- function planFileUpdate(cwd, input) {
3713
- const sourceContent = readFileSync12(input.sourcePath, "utf8");
4170
+ // src/install/diff.ts
4171
+ import { existsSync as existsSync16 } from "fs";
4172
+ import { join as join16 } from "path";
4173
+
4174
+ // src/install/file-update-plan.ts
4175
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4176
+ function planFileUpdate(input) {
4177
+ const sourceContent = readFileSync14(input.sourcePath, "utf8");
3714
4178
  const sourceHash = sha256(sourceContent);
3715
- const targetPath = resolveInside(cwd, input.target);
3716
- if (!existsSync14(targetPath)) {
3717
- return { target: input.target, action: "created", reason: "File is missing locally.", sourceContent };
4179
+ if (!existsSync15(input.targetPath)) {
4180
+ return { target: input.target, action: "created", reason: "File is missing locally.", sourceContent, sourceHash };
3718
4181
  }
3719
- const localHash = sha256(readFileSync12(targetPath, "utf8"));
4182
+ const localContent = readFileSync14(input.targetPath, "utf8");
4183
+ const localHash = sha256(localContent);
3720
4184
  if (localHash === sourceHash) {
3721
- return { target: input.target, action: "unchanged", reason: "File already matches the current template.", sourceContent };
4185
+ return {
4186
+ target: input.target,
4187
+ action: "unchanged",
4188
+ reason: "File already matches the current package asset.",
4189
+ sourceContent,
4190
+ sourceHash,
4191
+ localContent
4192
+ };
3722
4193
  }
3723
4194
  if (input.installedHash && localHash === input.installedHash) {
3724
- return { target: input.target, action: "updated", reason: "File was unmodified since install; applied the newer template.", sourceContent };
4195
+ return {
4196
+ target: input.target,
4197
+ action: "updated",
4198
+ reason: "File was unmodified since install; applied the newer package asset.",
4199
+ sourceContent,
4200
+ sourceHash,
4201
+ localContent
4202
+ };
3725
4203
  }
3726
4204
  if (input.force) {
3727
- return { target: input.target, action: "overwritten", reason: "Local changes overwritten because --force was used.", sourceContent };
4205
+ return {
4206
+ target: input.target,
4207
+ action: "overwritten",
4208
+ reason: "Local changes will be overwritten because --force was used.",
4209
+ sourceContent,
4210
+ sourceHash,
4211
+ localContent
4212
+ };
3728
4213
  }
3729
4214
  if (input.installedHash && input.installedHash === sourceHash) {
3730
- return { target: input.target, action: "kept-local", reason: "File is locally customized and the template has not changed.", sourceContent };
4215
+ return {
4216
+ target: input.target,
4217
+ action: "kept-local",
4218
+ reason: "File is locally customized and the package asset has not changed.",
4219
+ sourceContent,
4220
+ sourceHash,
4221
+ localContent
4222
+ };
3731
4223
  }
3732
4224
  return {
3733
4225
  target: input.target,
3734
4226
  action: "conflict",
3735
- reason: "File is locally customized and the bundled template changed; review the conflict copy.",
3736
- sourceContent
4227
+ reason: "File is locally customized and the package asset changed; review the proposed content.",
4228
+ sourceContent,
4229
+ sourceHash,
4230
+ localContent
3737
4231
  };
3738
4232
  }
3739
- function writeConflictCopy(cwd, target, content) {
3740
- const conflictRoot = join14(cwd, ".agent-kit", "conflicts");
3741
- const safeName = `${Date.now()}-${target.replace(/[^a-zA-Z0-9_.-]/g, "_")}`;
3742
- const conflictPath = join14(conflictRoot, safeName);
3743
- writeText(conflictPath, content);
3744
- return relative2(cwd, conflictPath).replace(/\\/g, "/");
4233
+
4234
+ // src/install/diff.ts
4235
+ function diffStatus(plan) {
4236
+ if (plan.action === "created") return "missing";
4237
+ if (plan.action === "unchanged") return "unchanged";
4238
+ return "changed";
4239
+ }
4240
+ function diffProject(cwd, stack = "next-supabase") {
4241
+ const packageRoot = findPackageRoot();
4242
+ const manifest = readManifest(cwd);
4243
+ const assets = listManagedAssets(packageRoot, stack);
4244
+ const plans = assets.map((asset) => ({
4245
+ asset,
4246
+ plan: planFileUpdate({
4247
+ target: asset.target,
4248
+ sourcePath: asset.sourcePath,
4249
+ targetPath: resolveInside(cwd, asset.target),
4250
+ installedHash: manifest?.assetHashes?.[asset.target] ?? manifest?.templateHashes?.[asset.target],
4251
+ force: false
4252
+ })
4253
+ }));
4254
+ const byTarget = new Map(plans.map(({ asset, plan }) => [asset.target, { asset, plan }]));
4255
+ const rootPlans = ROOT_DOCS.map((target) => byTarget.get(target)?.plan).filter((plan) => Boolean(plan));
4256
+ const agentPlan = byTarget.get(DEFAULT_AGENT_ROSTER_TARGET)?.plan;
4257
+ const modelPlan = byTarget.get(DEFAULT_MODEL_ROUTING_TARGET)?.plan;
4258
+ const mutatingActions = /* @__PURE__ */ new Set(["created", "updated", "overwritten"]);
4259
+ const refreshedFolders = [
4260
+ ...new Set(plans.filter(({ asset, plan }) => asset.libraryFolder && mutatingActions.has(plan.action)).map(({ asset }) => asset.libraryFolder))
4261
+ ].sort();
4262
+ const missingFolders = LIBRARY_FOLDERS.filter((folder) => !existsSync16(join16(cwd, ".agent-kit", folder)));
4263
+ const presentFolders = LIBRARY_FOLDERS.filter((folder) => !missingFolders.includes(folder));
4264
+ const wouldCreate = plans.filter(({ plan }) => plan.action === "created").map(({ plan }) => plan.target);
4265
+ const wouldWriteConflicts = plans.filter(({ plan }) => plan.action === "conflict").map(({ plan }) => plan.target);
4266
+ return {
4267
+ missing: rootPlans.filter((plan) => plan.action === "created").map((plan) => plan.target),
4268
+ unchanged: rootPlans.filter((plan) => plan.action === "unchanged").map((plan) => plan.target),
4269
+ changed: rootPlans.filter((plan) => plan.action !== "created" && plan.action !== "unchanged").map((plan) => plan.target),
4270
+ agentRoster: agentPlan ? diffStatus(agentPlan) : "missing",
4271
+ modelRouting: modelPlan ? diffStatus(modelPlan) : "missing",
4272
+ libraryFolders: {
4273
+ missing: missingFolders,
4274
+ present: presentFolders,
4275
+ willRefresh: refreshedFolders
4276
+ },
4277
+ preview: {
4278
+ wouldCreate,
4279
+ wouldWriteConflicts,
4280
+ wouldRefreshLibraryFolders: refreshedFolders,
4281
+ wouldCreateAgentRoster: agentPlan?.action === "created",
4282
+ wouldWriteAgentRosterConflict: agentPlan?.action === "conflict",
4283
+ wouldCreateModelRouting: modelPlan?.action === "created",
4284
+ wouldWriteModelRoutingConflict: modelPlan?.action === "conflict"
4285
+ }
4286
+ };
3745
4287
  }
4288
+
4289
+ // src/install/update.ts
4290
+ import { existsSync as existsSync17 } from "fs";
4291
+ import { join as join17 } from "path";
3746
4292
  function updateProject(options) {
3747
4293
  const cwd = options.cwd;
3748
4294
  const force = Boolean(options.force);
@@ -3755,14 +4301,14 @@ function updateProject(options) {
3755
4301
  const initResult = initProject({ cwd, force });
3756
4302
  const files2 = [
3757
4303
  ...initResult.copied.map((target) => ({ target, action: "created", reason: "Installed by init fallback." })),
3758
- ...initResult.unchanged.map((target) => ({ target, action: "unchanged", reason: "Already matched the template." })),
4304
+ ...initResult.unchanged.map((target) => ({ target, action: "unchanged", reason: "Already matched the package asset." })),
3759
4305
  ...initResult.overwritten.map((target) => ({ target, action: "overwritten", reason: "Overwritten by init --force fallback." })),
3760
4306
  ...initResult.conflicts.map((entry) => {
3761
4307
  const [target, conflictPath] = entry.split(" -> ");
3762
4308
  return {
3763
4309
  target: target ?? entry,
3764
4310
  action: "conflict",
3765
- reason: "Local file differed from the template during init fallback.",
4311
+ reason: "Local file differed from the package asset during init fallback.",
3766
4312
  ...conflictPath ? { conflictPath } : {}
3767
4313
  };
3768
4314
  })
@@ -3777,66 +4323,53 @@ function updateProject(options) {
3777
4323
  }
3778
4324
  const packageRoot = findPackageRoot();
3779
4325
  const stack = manifest.stack ?? "next-supabase";
3780
- const templateRoot = join14(packageRoot, "templates", stack);
3781
- if (!existsSync14(templateRoot)) {
3782
- throw new Error(`Unsupported stack profile in manifest: ${stack}`);
3783
- }
3784
- const files = [];
3785
- const templateHashes = {};
3786
- const plans = [];
3787
- for (const doc of ROOT_DOCS) {
3788
- const sourcePath = join14(templateRoot, doc);
3789
- templateHashes[doc] = sha256(readFileSync12(sourcePath, "utf8"));
3790
- plans.push(
3791
- planFileUpdate(cwd, {
3792
- target: doc,
3793
- sourcePath,
3794
- installedHash: manifest.templateHashes?.[doc],
3795
- force
3796
- })
3797
- );
3798
- }
3799
- for (const adapter2 of CURSOR_ADAPTER_FILES) {
3800
- plans.push(
3801
- planFileUpdate(cwd, {
3802
- target: adapter2.target,
3803
- sourcePath: join14(packageRoot, adapter2.source),
3804
- installedHash: void 0,
3805
- force
3806
- })
3807
- );
3808
- }
3809
- plans.push(
3810
- planFileUpdate(cwd, {
3811
- target: DEFAULT_AGENT_ROSTER_TARGET,
3812
- sourcePath: join14(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE),
3813
- installedHash: void 0,
3814
- force
3815
- }),
3816
- planFileUpdate(cwd, {
3817
- target: DEFAULT_MODEL_ROUTING_TARGET,
3818
- sourcePath: join14(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE),
3819
- installedHash: void 0,
4326
+ const templateRoot = join17(packageRoot, "templates", stack);
4327
+ if (!existsSync17(templateRoot)) throw new Error(`Unsupported stack profile in manifest: ${stack}`);
4328
+ const assets = listManagedAssets(packageRoot, stack);
4329
+ const plans = assets.map(
4330
+ (asset) => planFileUpdate({
4331
+ target: asset.target,
4332
+ sourcePath: asset.sourcePath,
4333
+ targetPath: resolveInside(cwd, asset.target),
4334
+ installedHash: manifest.assetHashes?.[asset.target] ?? manifest.templateHashes?.[asset.target],
3820
4335
  force
3821
4336
  })
3822
4337
  );
4338
+ const files = [];
3823
4339
  for (const plan of plans) {
3824
- const { sourceContent, ...fileResult } = plan;
4340
+ const result = { target: plan.target, action: plan.action, reason: plan.reason };
3825
4341
  if (!dryRun) {
3826
4342
  if (plan.action === "created" || plan.action === "updated" || plan.action === "overwritten") {
3827
- writeText(resolveInside(cwd, plan.target), sourceContent);
4343
+ writeText(resolveInside(cwd, plan.target), plan.sourceContent);
3828
4344
  } else if (plan.action === "conflict") {
3829
- fileResult.conflictPath = writeConflictCopy(cwd, plan.target, sourceContent);
4345
+ const proposal = writeConflictProposal(cwd, plan.target, plan.sourceContent, {
4346
+ currentContent: plan.localContent,
4347
+ reason: plan.reason,
4348
+ sourceVersion: PACKAGE_VERSION
4349
+ });
4350
+ result.conflictPath = proposal.conflictPath;
3830
4351
  }
3831
4352
  }
3832
- files.push(fileResult);
4353
+ files.push(result);
4354
+ }
4355
+ const currentTargets = new Set(assets.map((asset) => asset.target));
4356
+ for (const staleTarget of Object.keys(manifest.assetHashes ?? {}).filter((target) => !currentTargets.has(target)).sort()) {
4357
+ if (!existsSync17(resolveInside(cwd, staleTarget))) continue;
4358
+ files.push({
4359
+ target: staleTarget,
4360
+ action: "kept-local",
4361
+ reason: "The asset is no longer shipped; it was retained for explicit manual removal."
4362
+ });
3833
4363
  }
4364
+ const refreshedFolders = [
4365
+ ...new Set(
4366
+ assets.filter((asset, index) => asset.libraryFolder && ["created", "updated", "overwritten"].includes(plans[index]?.action ?? "")).map((asset) => asset.libraryFolder)
4367
+ )
4368
+ ].sort();
3834
4369
  if (!dryRun) {
3835
- ensureDir(join14(cwd, ".agent-kit"));
3836
- for (const folder of LIBRARY_FOLDERS) {
3837
- copyDirectory(join14(packageRoot, folder), join14(cwd, ".agent-kit", folder));
3838
- }
4370
+ const assetHashes = hashManagedAssets(assets);
3839
4371
  const updatedManifest = {
4372
+ schemaVersion: 2,
3840
4373
  packageName: PACKAGE_NAME,
3841
4374
  packageVersion: PACKAGE_VERSION,
3842
4375
  stack,
@@ -3844,17 +4377,18 @@ function updateProject(options) {
3844
4377
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3845
4378
  docs: [...ROOT_DOCS],
3846
4379
  libraryFolders: [...LIBRARY_FOLDERS],
3847
- agentRoster: DEFAULT_AGENT_ROSTER_TARGET,
3848
- modelRouting: DEFAULT_MODEL_ROUTING_TARGET,
3849
- templateHashes
4380
+ agentRoster: manifest.agentRoster ?? DEFAULT_AGENT_ROSTER_TARGET,
4381
+ modelRouting: manifest.modelRouting ?? DEFAULT_MODEL_ROUTING_TARGET,
4382
+ templateHashes: Object.fromEntries(ROOT_DOCS.map((doc) => [doc, assetHashes[doc] ?? ""])),
4383
+ assetHashes
3850
4384
  };
3851
- writeText(join14(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(updatedManifest, null, 2)}
4385
+ writeText(join17(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(updatedManifest, null, 2)}
3852
4386
  `);
3853
4387
  }
3854
4388
  return {
3855
4389
  dryRun,
3856
4390
  files,
3857
- libraryFoldersRefreshed: [...LIBRARY_FOLDERS],
4391
+ libraryFoldersRefreshed: refreshedFolders,
3858
4392
  manifestPath: ".agent-kit/manifest.json",
3859
4393
  summary: summarize(files)
3860
4394
  };
@@ -3874,8 +4408,8 @@ function summarize(files) {
3874
4408
 
3875
4409
  // src/research/discover.ts
3876
4410
  import { Octokit } from "@octokit/rest";
3877
- import { readFileSync as readFileSync13 } from "fs";
3878
- import { join as join15 } from "path";
4411
+ import { readFileSync as readFileSync15 } from "fs";
4412
+ import { join as join18 } from "path";
3879
4413
 
3880
4414
  // src/research/config.ts
3881
4415
  import { z as z2 } from "zod";
@@ -3900,8 +4434,8 @@ var researchConfigSchema = z2.object({
3900
4434
  // src/research/discover.ts
3901
4435
  async function discoverRepos(options) {
3902
4436
  const packageRoot = findPackageRoot();
3903
- const configPath = join15(packageRoot, "research", "scan-config.json");
3904
- const config = researchConfigSchema.parse(JSON.parse(readFileSync13(configPath, "utf8")));
4437
+ const configPath = join18(packageRoot, "research", "scan-config.json");
4438
+ const config = researchConfigSchema.parse(JSON.parse(readFileSync15(configPath, "utf8")));
3905
4439
  const token = options.token ?? process.env.GITHUB_TOKEN;
3906
4440
  if (!token) {
3907
4441
  throw new Error("GITHUB_TOKEN is required for GitHub API research discovery.");
@@ -3962,20 +4496,20 @@ async function discoverRepos(options) {
3962
4496
  }
3963
4497
  }
3964
4498
  const candidates = [...deduped.values()].slice(0, maxRepos);
3965
- const output = options.output ?? join15(options.cwd, "research", "repo-candidates.json");
4499
+ const output = options.output ?? join18(options.cwd, "research", "repo-candidates.json");
3966
4500
  writeText(output, `${JSON.stringify(candidates, null, 2)}
3967
4501
  `);
3968
4502
  return candidates;
3969
4503
  }
3970
4504
 
3971
4505
  // src/research/scan.ts
3972
- import { existsSync as existsSync16, mkdirSync as mkdirSync2, readFileSync as readFileSync15, rmSync } from "fs";
3973
- import { join as join17 } from "path";
4506
+ import { existsSync as existsSync19, mkdirSync as mkdirSync2, readFileSync as readFileSync17, rmSync as rmSync2 } from "fs";
4507
+ import { join as join20 } from "path";
3974
4508
  import { simpleGit } from "simple-git";
3975
4509
 
3976
4510
  // src/research/analyze.ts
3977
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
3978
- import { join as join16 } from "path";
4511
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4512
+ import { join as join19 } from "path";
3979
4513
  function normalizeRelativePath(file) {
3980
4514
  return file.replace(/\\/g, "/");
3981
4515
  }
@@ -3983,8 +4517,8 @@ function hasFile(files, matcher) {
3983
4517
  return files.some((file) => matcher.test(normalizeRelativePath(file)));
3984
4518
  }
3985
4519
  function fileText(root, file) {
3986
- const path = join16(root, file);
3987
- return existsSync15(path) ? readFileSync14(path, "utf8") : "";
4520
+ const path = join19(root, file);
4521
+ return existsSync18(path) ? readFileSync16(path, "utf8") : "";
3988
4522
  }
3989
4523
  function textIncludes(root, files, matcher, terms) {
3990
4524
  const lowerTerms = terms.map((term) => term.toLowerCase());
@@ -4115,34 +4649,34 @@ ${finding.impactOnKit.map((item) => `- ${item}`).join("\n")}
4115
4649
  `;
4116
4650
  }
4117
4651
  async function scanRepos(options) {
4118
- const candidatesPath = options.candidatesPath ?? join17(options.cwd, "research", "repo-candidates.json");
4119
- if (!existsSync16(candidatesPath)) {
4652
+ const candidatesPath = options.candidatesPath ?? join20(options.cwd, "research", "repo-candidates.json");
4653
+ if (!existsSync19(candidatesPath)) {
4120
4654
  throw new Error(`Candidates file not found: ${candidatesPath}`);
4121
4655
  }
4122
- const candidates = JSON.parse(readFileSync15(candidatesPath, "utf8"));
4123
- const workdir = options.workdir ?? join17(options.cwd, "research", "workdir");
4656
+ const candidates = JSON.parse(readFileSync17(candidatesPath, "utf8"));
4657
+ const workdir = options.workdir ?? join20(options.cwd, "research", "workdir");
4124
4658
  mkdirSync2(workdir, { recursive: true });
4125
- mkdirSync2(join17(options.cwd, "research", "findings"), { recursive: true });
4659
+ mkdirSync2(join20(options.cwd, "research", "findings"), { recursive: true });
4126
4660
  const findings = [];
4127
4661
  const git = simpleGit();
4128
4662
  for (const candidate of candidates) {
4129
4663
  const repoSlug = candidate.fullName.replace("/", "__");
4130
- const repoPath = join17(workdir, repoSlug);
4131
- if (existsSync16(repoPath)) rmSync(repoPath, { recursive: true, force: true });
4664
+ const repoPath = join20(workdir, repoSlug);
4665
+ if (existsSync19(repoPath)) rmSync2(repoPath, { recursive: true, force: true });
4132
4666
  await git.raw(["clone", "--depth", "1", candidate.htmlUrl, repoPath]);
4133
4667
  const finding = analyzeRepository(candidate, repoPath);
4134
4668
  findings.push(finding);
4135
- writeText(join17(options.cwd, "research", "findings", `${repoSlug}.md`), findingToMarkdown(finding));
4669
+ writeText(join20(options.cwd, "research", "findings", `${repoSlug}.md`), findingToMarkdown(finding));
4136
4670
  if (!options.keepClones) {
4137
- rmSync(repoPath, { recursive: true, force: true });
4671
+ rmSync2(repoPath, { recursive: true, force: true });
4138
4672
  }
4139
4673
  }
4140
4674
  return findings;
4141
4675
  }
4142
4676
 
4143
4677
  // src/research/summarize.ts
4144
- import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync3 } from "fs";
4145
- import { join as join18 } from "path";
4678
+ import { existsSync as existsSync20, readFileSync as readFileSync18, readdirSync as readdirSync3 } from "fs";
4679
+ import { join as join21 } from "path";
4146
4680
  var SUMMARY_TARGETS = {
4147
4681
  "nextjs-patterns": {
4148
4682
  title: "Next.js Patterns",
@@ -4245,12 +4779,12 @@ function renderRepoList(findings, scoreKeys) {
4245
4779
  return findings.slice().sort((a, b) => scoreFor(b, scoreKeys) - scoreFor(a, scoreKeys) || b.totalScore - a.totalScore || b.stars - a.stars).slice(0, 12).map((finding) => `- ${finding.fullName} (${finding.category}) - focus score ${scoreFor(finding, scoreKeys)}, total ${finding.totalScore}/${maxTotalScore}`).join("\n");
4246
4780
  }
4247
4781
  function summarizeFindings(cwd) {
4248
- const findingsDir = join18(cwd, "research", "findings");
4249
- if (!existsSync17(findingsDir)) {
4782
+ const findingsDir = join21(cwd, "research", "findings");
4783
+ if (!existsSync20(findingsDir)) {
4250
4784
  throw new Error("No research/findings directory exists. Run agent-kit research scan first.");
4251
4785
  }
4252
4786
  const findingFiles = readdirSync3(findingsDir).filter((file) => file.endsWith(".md"));
4253
- const findings = findingFiles.map((file) => parseFinding(file, readFileSync16(join18(findingsDir, file), "utf8"))).filter((finding) => finding !== null);
4787
+ const findings = findingFiles.map((file) => parseFinding(file, readFileSync18(join21(findingsDir, file), "utf8"))).filter((finding) => finding !== null);
4254
4788
  const categoryCounts = countBy(findings.map((finding) => finding.category));
4255
4789
  const outputs = [];
4256
4790
  const overview = `# Research Scan Overview
@@ -4269,13 +4803,13 @@ ${countBy(findings.flatMap((finding) => finding.strongPractices)).slice(0, 12).m
4269
4803
  ## Most Repeated Gaps
4270
4804
  ${countBy(findings.flatMap((finding) => finding.weakPractices)).slice(0, 12).map(([practice, count]) => `- ${practice} (${count})`).join("\n")}
4271
4805
  `;
4272
- const overviewPath = join18(cwd, "research", "summaries", "scan-overview.md");
4806
+ const overviewPath = join21(cwd, "research", "summaries", "scan-overview.md");
4273
4807
  writeText(overviewPath, overview);
4274
4808
  outputs.push(overviewPath);
4275
4809
  for (const [target, config] of Object.entries(SUMMARY_TARGETS)) {
4276
4810
  const categories = config.categories;
4277
4811
  const scopedFindings = findings.filter((finding) => categories.includes(finding.category));
4278
- const path = join18(cwd, "research", "summaries", `${target}.md`);
4812
+ const path = join21(cwd, "research", "summaries", `${target}.md`);
4279
4813
  const summary2 = `# ${config.title}
4280
4814
 
4281
4815
  Generated from ${scopedFindings.length} relevant repository findings.
@@ -4316,7 +4850,7 @@ Review the generated research summaries, then convert repeated best practices in
4316
4850
 
4317
4851
  Do not copy source code from scanned repositories. Adopt only generalized practices with clear rationale.
4318
4852
  `;
4319
- const path = join18(cwd, "research", "proposed-updates.md");
4853
+ const path = join21(cwd, "research", "proposed-updates.md");
4320
4854
  writeText(path, output);
4321
4855
  return path;
4322
4856
  }
@@ -4433,8 +4967,9 @@ function proposeCorrectionUpstream(cwd, id) {
4433
4967
  }
4434
4968
 
4435
4969
  // src/studio/session.ts
4436
- import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
4437
- import { join as join19 } from "path";
4970
+ import { randomUUID as randomUUID2 } from "crypto";
4971
+ import { existsSync as existsSync21, readFileSync as readFileSync19, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
4972
+ import { join as join22 } from "path";
4438
4973
  function sessionDir(sessionId) {
4439
4974
  return `${COUNCIL_SESSIONS_DIR}/${safeSlug(sessionId)}`;
4440
4975
  }
@@ -4450,6 +4985,9 @@ function indexPath(sessionId) {
4450
4985
  function transcriptPath(sessionId) {
4451
4986
  return `${sessionDir(sessionId)}/transcript.md`;
4452
4987
  }
4988
+ function sessionLockPath(sessionId) {
4989
+ return `${sessionDir(sessionId)}/.session.lock`;
4990
+ }
4453
4991
  function readDefaultWorkflowOutputs(cwd, workflowId) {
4454
4992
  const roster = readJsonFile(cwd, DEFAULT_AGENT_ROSTER_TARGET);
4455
4993
  const workflow = roster?.workflows?.find((item) => item.id === workflowId);
@@ -4461,7 +4999,7 @@ function readDefaultWorkflowOutputs(cwd, workflowId) {
4461
4999
  function startSession(cwd, options) {
4462
5000
  ensureStudioDirs(cwd);
4463
5001
  const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4464
- const sessionId = safeSlug(`${datePrefix}-${options.title}`);
5002
+ const sessionId = safeSlug(`${datePrefix}-${options.title}-${randomUUID2().slice(0, 8)}`);
4465
5003
  const now = nowIso();
4466
5004
  const workflowId = options.workflowId ?? "planning";
4467
5005
  const session2 = {
@@ -4491,9 +5029,9 @@ function startSession(cwd, options) {
4491
5029
  return { sessionId, sessionPath: sessionDir(sessionId) };
4492
5030
  }
4493
5031
  function listSessions(cwd) {
4494
- const root = join19(cwd, COUNCIL_SESSIONS_DIR);
4495
- if (!existsSync18(root)) return [];
4496
- return readdirSync4(root).filter((entry) => entry !== "active").map((entry) => join19(root, entry, "session.json")).filter((path) => existsSync18(path) && statSync2(path).isFile()).map((path) => StudioSessionContract.parse(JSON.parse(readFileSync17(path, "utf8")))).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
5032
+ const root = join22(cwd, COUNCIL_SESSIONS_DIR);
5033
+ if (!existsSync21(root)) return [];
5034
+ return readdirSync4(root).filter((entry) => entry !== "active").map((entry) => join22(root, entry, "session.json")).filter((path) => existsSync21(path) && statSync4(path).isFile()).map((path) => StudioSessionContract.parse(JSON.parse(readFileSync19(path, "utf8")))).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
4497
5035
  }
4498
5036
  function getActiveSessionId(cwd) {
4499
5037
  const active = readTextFile(cwd, ACTIVE_SESSION_FILE)?.trim();
@@ -4519,7 +5057,18 @@ function writeSession(cwd, session2) {
4519
5057
  writeJsonFile(cwd, sessionJsonPath(session2.sessionId), StudioSessionContract.parse(session2));
4520
5058
  }
4521
5059
  function appendSessionEvent(cwd, sessionId, event) {
4522
- const parsed = SessionEventContract.parse(redactEvent(event));
5060
+ return withFileLock(cwd, sessionLockPath(sessionId), () => appendSessionEventUnlocked(cwd, sessionId, event));
5061
+ }
5062
+ function appendSessionEventUnlocked(cwd, sessionId, event) {
5063
+ const existingEvents = readSessionEvents(cwd, sessionId);
5064
+ const sequence = existingEvents.reduce((highest, item) => Math.max(highest, item.sequence ?? 0), 0) + 1;
5065
+ const parsed = SessionEventContract.parse(
5066
+ redactEvent({
5067
+ ...event,
5068
+ eventId: event.eventId ?? randomUUID2(),
5069
+ sequence: event.sequence ?? sequence
5070
+ })
5071
+ );
4523
5072
  appendJsonLine(cwd, eventsPath(sessionId), parsed);
4524
5073
  const session2 = readSession(cwd, sessionId);
4525
5074
  const updated = {
@@ -4548,7 +5097,10 @@ function redactEvent(event) {
4548
5097
  };
4549
5098
  }
4550
5099
  function recordNote(cwd, agentId, text) {
4551
- return appendSessionEvent(cwd, getActiveSessionId(cwd), { type: "agent_message", createdAt: nowIso(), agentId, text });
5100
+ return recordSessionNote(cwd, getActiveSessionId(cwd), agentId, text);
5101
+ }
5102
+ function recordSessionNote(cwd, sessionId, agentId, text) {
5103
+ return appendSessionEvent(cwd, sessionId, { type: "agent_message", createdAt: nowIso(), agentId, text });
4552
5104
  }
4553
5105
  function recordDecision(cwd, agentId, decision, risk) {
4554
5106
  return appendSessionEvent(cwd, getActiveSessionId(cwd), {
@@ -4609,22 +5161,24 @@ function recordRequiredOutput(cwd, name, status, evidence) {
4609
5161
  const trimmedName = name.trim();
4610
5162
  if (!trimmedName) throw new Error("Required output name is required.");
4611
5163
  const sessionId = getActiveSessionId(cwd);
4612
- const session2 = readSession(cwd, sessionId);
4613
- const now = nowIso();
4614
- const output = {
4615
- name: trimmedName,
4616
- status,
4617
- ...evidence ? { evidence: redactSensitive(evidence) } : {}
4618
- };
4619
- const outputIndex = session2.requiredOutputs.findIndex((item) => item.name === trimmedName);
4620
- const requiredOutputs = outputIndex === -1 ? [...session2.requiredOutputs, output] : session2.requiredOutputs.map((item, index) => index === outputIndex ? { ...item, ...output } : item);
4621
- writeSession(cwd, { ...session2, requiredOutputs, updatedAt: now });
4622
- return appendSessionEvent(cwd, sessionId, {
4623
- type: "required_output_updated",
4624
- createdAt: now,
4625
- outputName: trimmedName,
4626
- outputStatus: status,
4627
- ...evidence ? { evidence: [evidence] } : {}
5164
+ return withFileLock(cwd, sessionLockPath(sessionId), () => {
5165
+ const session2 = readSession(cwd, sessionId);
5166
+ const now = nowIso();
5167
+ const output = {
5168
+ name: trimmedName,
5169
+ status,
5170
+ ...evidence ? { evidence: redactSensitive(evidence) } : {}
5171
+ };
5172
+ const outputIndex = session2.requiredOutputs.findIndex((item) => item.name === trimmedName);
5173
+ const requiredOutputs = outputIndex === -1 ? [...session2.requiredOutputs, output] : session2.requiredOutputs.map((item, index) => index === outputIndex ? { ...item, ...output } : item);
5174
+ writeSession(cwd, { ...session2, requiredOutputs, updatedAt: now });
5175
+ return appendSessionEventUnlocked(cwd, sessionId, {
5176
+ type: "required_output_updated",
5177
+ createdAt: now,
5178
+ outputName: trimmedName,
5179
+ outputStatus: status,
5180
+ ...evidence ? { evidence: [evidence] } : {}
5181
+ });
4628
5182
  });
4629
5183
  }
4630
5184
  function closeSession(cwd, status) {
@@ -4641,14 +5195,16 @@ function renderActiveSession(cwd) {
4641
5195
  return renderSession(cwd, getActiveSessionId(cwd));
4642
5196
  }
4643
5197
  function renderSession(cwd, sessionId) {
4644
- const session2 = readSession(cwd, sessionId);
4645
- const events = readSessionEvents(cwd, sessionId);
4646
- const renderedAt = nowIso();
4647
- const updated = { ...session2, renderedAt, updatedAt: renderedAt };
4648
- writeTextFile(cwd, indexPath(sessionId), renderSessionIndex(updated, events));
4649
- writeTextFile(cwd, transcriptPath(sessionId), renderSessionTranscript(updated, events));
4650
- writeSession(cwd, updated);
4651
- return { sessionId, sessionPath: sessionDir(sessionId) };
5198
+ return withFileLock(cwd, sessionLockPath(sessionId), () => {
5199
+ const session2 = readSession(cwd, sessionId);
5200
+ const events = readSessionEvents(cwd, sessionId);
5201
+ const renderedAt = nowIso();
5202
+ const updated = { ...session2, renderedAt, updatedAt: renderedAt };
5203
+ writeTextFile(cwd, indexPath(sessionId), renderSessionIndex(updated, events));
5204
+ writeTextFile(cwd, transcriptPath(sessionId), renderSessionTranscript(updated, events));
5205
+ writeSession(cwd, updated);
5206
+ return { sessionId, sessionPath: sessionDir(sessionId) };
5207
+ });
4652
5208
  }
4653
5209
  function renderSessionIndex(session2, events) {
4654
5210
  const handoffs = events.filter((event) => event.type === "handoff");
@@ -4707,7 +5263,7 @@ ${verification.map((event) => `| ${escapeMarkdownTableCell(event.command)} | ${e
4707
5263
  ## Next Actions
4708
5264
 
4709
5265
  ${renderNextActions(session2, verification)}
4710
- `;
5266
+ `.trimEnd() + "\n";
4711
5267
  }
4712
5268
  function renderDecisionRow(event) {
4713
5269
  if (event.type === "handoff") {
@@ -4762,7 +5318,7 @@ ${rows.join("\n")}`;
4762
5318
  Generated from \`${sessionDir(session2.sessionId)}/events.jsonl\`.
4763
5319
 
4764
5320
  ${sections || "No events recorded."}
4765
- `;
5321
+ `.trimEnd() + "\n";
4766
5322
  }
4767
5323
 
4768
5324
  // src/studio/export.ts
@@ -5025,19 +5581,19 @@ function safeJsonForHtml(value) {
5025
5581
  }
5026
5582
 
5027
5583
  // src/studio/setup-browser.ts
5028
- import { execFileSync } from "child_process";
5584
+ import { execFileSync as execFileSync2 } from "child_process";
5029
5585
  function openBrowser(url) {
5030
5586
  const platform = process.platform;
5031
5587
  try {
5032
5588
  if (platform === "darwin") {
5033
- execFileSync("open", [url], { stdio: "ignore" });
5589
+ execFileSync2("open", [url], { stdio: "ignore" });
5034
5590
  return;
5035
5591
  }
5036
5592
  if (platform === "win32") {
5037
- execFileSync("cmd", ["/c", "start", "", url], { stdio: "ignore" });
5593
+ execFileSync2("cmd", ["/c", "start", "", url], { stdio: "ignore" });
5038
5594
  return;
5039
5595
  }
5040
- execFileSync("xdg-open", [url], { stdio: "ignore" });
5596
+ execFileSync2("xdg-open", [url], { stdio: "ignore" });
5041
5597
  } catch {
5042
5598
  console.log(`Open this URL in your browser: ${url}`);
5043
5599
  }
@@ -5049,10 +5605,10 @@ async function promptStartSetup(defaultYes = true) {
5049
5605
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
5050
5606
  const rl = createInterface({ input: process.stdin, output: process.stdout });
5051
5607
  const question = defaultYes ? "Start setup wizard now? [Y/n] " : "Start setup wizard now? [y/N] ";
5052
- const answer = await new Promise((resolve3) => {
5608
+ const answer = await new Promise((resolve4) => {
5053
5609
  rl.question(question, (value) => {
5054
5610
  rl.close();
5055
- resolve3(value.trim().toLowerCase());
5611
+ resolve4(value.trim().toLowerCase());
5056
5612
  });
5057
5613
  });
5058
5614
  if (!answer) return defaultYes;
@@ -5171,8 +5727,8 @@ function parseSetupFormPayload(raw) {
5171
5727
  }
5172
5728
 
5173
5729
  // src/studio/wizard/checklist.ts
5174
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
5175
- import { join as join20 } from "path";
5730
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
5731
+ import { join as join23 } from "path";
5176
5732
  var IDE_PATHS = {
5177
5733
  cursor: ".cursor/agents/planner.md",
5178
5734
  copilot: ".github/copilot-instructions.md",
@@ -5193,12 +5749,12 @@ function saveIdeChecklist(cwd, ideSurface) {
5193
5749
  function detectIdeRulePresent(cwd, ideSurface) {
5194
5750
  const rel = IDE_PATHS[ideSurface];
5195
5751
  if (ideSurface === "cursor") {
5196
- return existsSync19(join20(cwd, rel)) || existsSync19(join20(cwd, ".cursor/rules/cursor-agent-kit.mdc"));
5752
+ return existsSync22(join23(cwd, rel)) || existsSync22(join23(cwd, ".cursor/rules/cursor-agent-kit.mdc"));
5197
5753
  }
5198
5754
  if (rel.endsWith("/")) {
5199
- return existsSync19(join20(cwd, rel));
5755
+ return existsSync22(join23(cwd, rel));
5200
5756
  }
5201
- return existsSync19(join20(cwd, rel));
5757
+ return existsSync22(join23(cwd, rel));
5202
5758
  }
5203
5759
  var VISUAL_QA_MARKER = "## Visual QA Tier";
5204
5760
  var VISUAL_QA_BLOCKS = {
@@ -5226,11 +5782,11 @@ This project uses the **Mature** visual QA tier.
5226
5782
  };
5227
5783
  function writeVisualQaTier(cwd, tier) {
5228
5784
  const path = "TESTING.md";
5229
- const fullPath = join20(cwd, path);
5230
- if (!existsSync19(fullPath)) {
5785
+ const fullPath = join23(cwd, path);
5786
+ if (!existsSync22(fullPath)) {
5231
5787
  return { updated: false, path, reason: "TESTING.md not found in project root." };
5232
5788
  }
5233
- const current = readFileSync18(fullPath, "utf8");
5789
+ const current = readFileSync20(fullPath, "utf8");
5234
5790
  if (current.includes(VISUAL_QA_MARKER)) {
5235
5791
  return {
5236
5792
  updated: false,
@@ -5296,7 +5852,7 @@ function renderAgentBriefsMarkdown(cwd, file) {
5296
5852
  const text = file.briefs[agent.id]?.trim();
5297
5853
  if (!text) continue;
5298
5854
  wroteAny = true;
5299
- lines.push(`## ${agent.name}`, "", `**Role:** ${agent.roleSummary}`, "", text, "");
5855
+ lines.push(`## ${escapeMarkdownText(agent.name)}`, "", `**Role:** ${escapeMarkdownText(agent.roleSummary)}`, "", escapeMarkdownText(text), "");
5300
5856
  }
5301
5857
  if (!wroteAny) {
5302
5858
  lines.push("_No agent briefs recorded yet. Run `agent-kit setup` to brief your team._", "");
@@ -5406,8 +5962,8 @@ function extractSetupFormFromWizardForm(form) {
5406
5962
  }
5407
5963
 
5408
5964
  // src/studio/wizard/drafts.ts
5409
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
5410
- import { join as join21 } from "path";
5965
+ import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5966
+ import { join as join24 } from "path";
5411
5967
  var DESIGN_DRAFT_JSON = ".agent-kit/onboarding/design-draft.json";
5412
5968
  var MESSAGING_DRAFT_JSON = ".agent-kit/onboarding/messaging-draft.json";
5413
5969
  function loadDesignDraft(cwd) {
@@ -5433,12 +5989,12 @@ function previewDesignMarkdown(draft) {
5433
5989
 
5434
5990
  | Area | Wizard draft |
5435
5991
  | --- | --- |
5436
- | Primary audience | ${draft.audience.trim() || "TBD"} |
5437
- | Content inventory | ${draft.contentInventory.trim() || "TBD"} |
5992
+ | Primary audience | ${escapeMarkdownTableCell(draft.audience.trim() || "TBD")} |
5993
+ | Content inventory | ${escapeMarkdownTableCell(draft.contentInventory.trim() || "TBD")} |
5438
5994
 
5439
5995
  ## Anti-References (wizard draft)
5440
5996
 
5441
- ${draft.antiReferences.trim() || "- TBD: pattern to avoid."}
5997
+ ${escapeMarkdownText(draft.antiReferences.trim() || "- TBD: pattern to avoid.")}
5442
5998
  `;
5443
5999
  }
5444
6000
  function previewMessagingMarkdown(draft) {
@@ -5446,24 +6002,29 @@ function previewMessagingMarkdown(draft) {
5446
6002
 
5447
6003
  | Question | Current Answer |
5448
6004
  | --- | --- |
5449
- | Who is the primary audience? | ${draft.audience.trim() || "TBD"} |
5450
- | What painful problem do they need solved? | ${draft.pain.trim() || "TBD"} |
5451
- | What outcome do they want? | ${draft.outcome.trim() || "TBD"} |
6005
+ | Who is the primary audience? | ${escapeMarkdownTableCell(draft.audience.trim() || "TBD")} |
6006
+ | What painful problem do they need solved? | ${escapeMarkdownTableCell(draft.pain.trim() || "TBD")} |
6007
+ | What outcome do they want? | ${escapeMarkdownTableCell(draft.outcome.trim() || "TBD")} |
5452
6008
  `;
5453
6009
  }
5454
6010
  function appendSectionToDoc(cwd, doc, sectionMarkdown) {
5455
- const fullPath = join21(cwd, doc);
5456
- if (!existsSync20(fullPath)) {
6011
+ const fullPath = join24(cwd, doc);
6012
+ if (!existsSync23(fullPath)) {
5457
6013
  return { target: doc, action: "missing" };
5458
6014
  }
5459
- const current = readFileSync19(fullPath, "utf8");
5460
- if (current.includes("(wizard draft)")) {
5461
- return { target: doc, action: "conflict", conflictPath: `.agent-kit/conflicts/wizard-${doc}` };
5462
- }
5463
- writeTextFile(cwd, doc, `${current.trimEnd()}
6015
+ const current = readFileSync21(fullPath, "utf8");
6016
+ const proposed = `${current.trimEnd()}
5464
6017
 
5465
6018
  ${sectionMarkdown.trim()}
5466
- `);
6019
+ `;
6020
+ if (current.includes("(wizard draft)")) {
6021
+ const conflict = writeConflictProposal(cwd, doc, proposed, {
6022
+ currentContent: current,
6023
+ reason: "The target already contains an applied wizard draft."
6024
+ });
6025
+ return { target: doc, action: "conflict", conflictPath: conflict.conflictPath };
6026
+ }
6027
+ writeTextFile(cwd, doc, proposed);
5467
6028
  return { target: doc, action: "appended" };
5468
6029
  }
5469
6030
  function applyDesignDraft(cwd) {
@@ -5495,8 +6056,8 @@ function applyDrafts(cwd) {
5495
6056
  }
5496
6057
 
5497
6058
  // src/studio/office/render.ts
5498
- import { readFileSync as readFileSync20 } from "fs";
5499
- import { join as join22 } from "path";
6059
+ import { readFileSync as readFileSync22 } from "fs";
6060
+ import { join as join25 } from "path";
5500
6061
 
5501
6062
  // src/studio/office/map.ts
5502
6063
  var MAP_WIDTH = 28;
@@ -5618,12 +6179,12 @@ var PRODUCT_CATEGORIES = ["content-app", "saas", "admin", "marketplace", "tool",
5618
6179
  var TENANT_MODELS = ["single-user", "team", "tenant", "marketplace", "admin", "public-content"];
5619
6180
  function readOfficeAsset(name) {
5620
6181
  const root = findPackageRoot();
5621
- const distPath = join22(root, "dist", "studio", "office", "assets", name);
5622
- const srcPath = join22(root, "src", "studio", "office", "assets", name);
6182
+ const distPath = join25(root, "dist", "studio", "office", "assets", name);
6183
+ const srcPath = join25(root, "src", "studio", "office", "assets", name);
5623
6184
  try {
5624
- return readFileSync20(distPath, "utf8");
6185
+ return readFileSync22(distPath, "utf8");
5625
6186
  } catch {
5626
- return readFileSync20(srcPath, "utf8");
6187
+ return readFileSync22(srcPath, "utf8");
5627
6188
  }
5628
6189
  }
5629
6190
  function buildOfficeBootConfig(cwd, viewModel) {
@@ -5668,6 +6229,51 @@ function renderOfficeHtml(boot, mode) {
5668
6229
  const isStudio = mode === "studio";
5669
6230
  const title = isStudio ? "Agent Kit \u2014 Live Studio" : "Agent Kit \u2014 Setup Office";
5670
6231
  const dataView = isStudio ? "studio-v1" : "office-v1";
6232
+ const studioAside = `<aside class="transcript-panel" id="transcript-panel" aria-label="Agent Studio activity">
6233
+ <div class="studio-tabs" role="tablist" aria-label="Studio view">
6234
+ <button type="button" class="studio-tab active" id="studio-council-tab" role="tab" aria-selected="true" aria-controls="studio-council-view">Council</button>
6235
+ <button type="button" class="studio-tab" id="studio-runtime-tab" role="tab" aria-selected="false" aria-controls="studio-runtime-view">Runs</button>
6236
+ </div>
6237
+ <section id="studio-council-view" role="tabpanel" aria-labelledby="studio-council-tab">
6238
+ <div class="studio-controls" id="studio-controls">
6239
+ <label class="studio-label" for="session-picker">Session</label>
6240
+ <select id="session-picker" aria-label="Council session"></select>
6241
+ <form id="studio-note-form" class="studio-note-form">
6242
+ <select id="studio-note-agent" aria-label="Agent for note"></select>
6243
+ <input id="studio-note-text" type="text" maxlength="3999" placeholder="Add council note\u2026" />
6244
+ <button type="submit" class="btn secondary">Add note</button>
6245
+ </form>
6246
+ <button type="button" class="btn primary" id="studio-render-btn">Render markdown</button>
6247
+ </div>
6248
+ <h2>Transcript</h2>
6249
+ <ol id="transcript-list"></ol>
6250
+ </section>
6251
+ <section id="studio-runtime-view" role="tabpanel" aria-labelledby="studio-runtime-tab" hidden>
6252
+ <form id="runtime-start-form" class="runtime-start-form">
6253
+ <label class="studio-label" for="runtime-goal">Goal</label>
6254
+ <textarea id="runtime-goal" maxlength="20000" rows="4" required></textarea>
6255
+ <label class="runtime-check"><input id="runtime-dirty-base" type="checkbox" /> Exclude current local changes</label>
6256
+ <button type="submit" class="btn primary" id="runtime-start-btn" disabled>Start run</button>
6257
+ </form>
6258
+ <div class="runtime-toolbar">
6259
+ <label class="studio-label" for="runtime-picker">Run</label>
6260
+ <select id="runtime-picker" aria-label="Runtime run"></select>
6261
+ <button type="button" class="btn secondary" id="runtime-refresh-btn">Refresh</button>
6262
+ </div>
6263
+ <dl class="runtime-summary" id="runtime-summary"></dl>
6264
+ <div class="runtime-approval" id="runtime-approval" hidden>
6265
+ <strong id="runtime-approval-title"></strong>
6266
+ <p id="runtime-approval-detail"></p>
6267
+ <div class="runtime-actions">
6268
+ <button type="button" class="btn primary" id="runtime-approve-btn">Approve</button>
6269
+ <button type="button" class="btn secondary" id="runtime-reject-btn">Reject</button>
6270
+ </div>
6271
+ </div>
6272
+ <button type="button" class="btn secondary runtime-cancel" id="runtime-cancel-btn" hidden>Cancel run</button>
6273
+ <h2>Run events</h2>
6274
+ <ol id="runtime-event-list"></ol>
6275
+ </section>
6276
+ </aside>`;
5671
6277
  return `<!doctype html>
5672
6278
  <html lang="en">
5673
6279
  <head>
@@ -5709,7 +6315,7 @@ function renderOfficeHtml(boot, mode) {
5709
6315
  <div id="nameplate-layer" class="nameplate-layer" aria-hidden="true"></div>
5710
6316
  <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
6317
  </div>
5712
- ${isStudio ? '<aside class="transcript-panel" id="transcript-panel" aria-label="Session transcript"><h2>Transcript</h2><ol id="transcript-list"></ol></aside>' : ""}
6318
+ ${isStudio ? studioAside : ""}
5713
6319
  </main>
5714
6320
  <div id="status" class="status" role="status" aria-live="polite"></div>
5715
6321
  <div id="depth-modal" class="modal modal-blur" hidden>
@@ -5795,18 +6401,18 @@ function allAgentBriefsComplete(form, agentIds) {
5795
6401
  }
5796
6402
 
5797
6403
  // src/studio/wizard/render.ts
5798
- import { readFileSync as readFileSync21 } from "fs";
5799
- import { join as join23 } from "path";
6404
+ import { readFileSync as readFileSync23 } from "fs";
6405
+ import { join as join26 } from "path";
5800
6406
  var PRODUCT_CATEGORIES2 = ["content-app", "saas", "admin", "marketplace", "tool", "ecommerce", "portfolio", "education", "community", "ai-workflow", "other"];
5801
6407
  var TENANT_MODELS2 = ["single-user", "team", "tenant", "marketplace", "admin", "public-content"];
5802
6408
  function readWizardAsset(name) {
5803
6409
  const root = findPackageRoot();
5804
- const distPath = join23(root, "dist", "studio", "wizard", "assets", name);
5805
- const srcPath = join23(root, "src", "studio", "wizard", "assets", name);
6410
+ const distPath = join26(root, "dist", "studio", "wizard", "assets", name);
6411
+ const srcPath = join26(root, "src", "studio", "wizard", "assets", name);
5806
6412
  try {
5807
- return readFileSync21(distPath, "utf8");
6413
+ return readFileSync23(distPath, "utf8");
5808
6414
  } catch {
5809
- return readFileSync21(srcPath, "utf8");
6415
+ return readFileSync23(srcPath, "utf8");
5810
6416
  }
5811
6417
  }
5812
6418
  function mergeWizardSteps(cwd) {
@@ -5896,12 +6502,12 @@ function renderSetupWizardHtmlWithContext(cwd) {
5896
6502
  }
5897
6503
 
5898
6504
  // src/studio/agentic-level.ts
5899
- import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
5900
- import { join as join24 } from "path";
6505
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "fs";
6506
+ import { join as join27 } from "path";
5901
6507
  var CACHE_TTL_MS = 3e4;
5902
6508
  var cache = /* @__PURE__ */ new Map();
5903
6509
  function isMaintainerSourceRepo(cwd) {
5904
- return existsSync21(join24(cwd, "package.json")) && existsSync21(join24(cwd, "src")) && existsSync21(join24(cwd, "templates"));
6510
+ return existsSync24(join27(cwd, "package.json")) && existsSync24(join27(cwd, "src")) && existsSync24(join27(cwd, "templates"));
5905
6511
  }
5906
6512
  function signal(id, level, label, pass, evidence, remediation) {
5907
6513
  return { id, level, label, pass, evidence, remediation };
@@ -5917,23 +6523,23 @@ function detectIdePresent(cwd) {
5917
6523
  return { pass: true, evidence: `${surface} adapter files detected` };
5918
6524
  }
5919
6525
  }
5920
- if (existsSync21(join24(cwd, ".cursor/rules/cursor-agent-kit.mdc"))) {
6526
+ if (existsSync24(join27(cwd, ".cursor/rules/cursor-agent-kit.mdc"))) {
5921
6527
  return { pass: true, evidence: "Cursor council rules from init" };
5922
6528
  }
5923
6529
  return { pass: false, evidence: "No IDE adapter rules or subagents detected" };
5924
6530
  }
5925
6531
  function detectTierBSubagents(cwd) {
5926
6532
  const paths = [".cursor/agents/planner.md", ".codex/agents/planner.toml", ".claude/agents/planner.md", ".github/copilot-instructions.md"];
5927
- const found = paths.filter((rel) => existsSync21(join24(cwd, rel)));
6533
+ const found = paths.filter((rel) => existsSync24(join27(cwd, rel)));
5928
6534
  if (found.length > 0) {
5929
6535
  return { pass: true, evidence: `Specialist surface: ${found[0]}` };
5930
6536
  }
5931
6537
  return { pass: false, evidence: "No council subagents or Copilot instructions installed" };
5932
6538
  }
5933
6539
  function readDocSnippet(cwd, name, needles) {
5934
- const path = join24(cwd, name);
5935
- if (!existsSync21(path)) return false;
5936
- const lower = readFileSync22(path, "utf8").toLowerCase();
6540
+ const path = join27(cwd, name);
6541
+ if (!existsSync24(path)) return false;
6542
+ const lower = readFileSync24(path, "utf8").toLowerCase();
5937
6543
  return needles.every((needle) => lower.includes(needle.toLowerCase()));
5938
6544
  }
5939
6545
  function adapterTargetForIde(ide) {
@@ -5961,8 +6567,8 @@ function buildSignals(cwd, maintainerProfile) {
5961
6567
  "l4-agents-md",
5962
6568
  4,
5963
6569
  "Council contract (AGENTS.md)",
5964
- existsSync21(join24(cwd, "AGENTS.md")),
5965
- existsSync21(join24(cwd, "AGENTS.md")) ? "AGENTS.md installed" : "AGENTS.md missing",
6570
+ existsSync24(join27(cwd, "AGENTS.md")),
6571
+ existsSync24(join27(cwd, "AGENTS.md")) ? "AGENTS.md installed" : "AGENTS.md missing",
5966
6572
  "Run agent-kit init --stack next-supabase"
5967
6573
  )
5968
6574
  );
@@ -5971,8 +6577,8 @@ function buildSignals(cwd, maintainerProfile) {
5971
6577
  "l4-adapters-doc",
5972
6578
  4,
5973
6579
  "Assistant activation doc",
5974
- existsSync21(join24(cwd, "ASSISTANT_ADAPTERS.md")),
5975
- existsSync21(join24(cwd, "ASSISTANT_ADAPTERS.md")) ? "ASSISTANT_ADAPTERS.md installed" : "ASSISTANT_ADAPTERS.md missing",
6580
+ existsSync24(join27(cwd, "ASSISTANT_ADAPTERS.md")),
6581
+ existsSync24(join27(cwd, "ASSISTANT_ADAPTERS.md")) ? "ASSISTANT_ADAPTERS.md installed" : "ASSISTANT_ADAPTERS.md missing",
5976
6582
  "Run agent-kit init or agent-kit update"
5977
6583
  )
5978
6584
  );
@@ -5981,8 +6587,8 @@ function buildSignals(cwd, maintainerProfile) {
5981
6587
  "l4-roster",
5982
6588
  4,
5983
6589
  "Machine-readable council roster",
5984
- existsSync21(join24(cwd, ".agent-kit/agent-roster.json")),
5985
- existsSync21(join24(cwd, ".agent-kit/agent-roster.json")) ? ".agent-kit/agent-roster.json present" : "Roster missing",
6590
+ existsSync24(join27(cwd, ".agent-kit/agent-roster.json")),
6591
+ existsSync24(join27(cwd, ".agent-kit/agent-roster.json")) ? ".agent-kit/agent-roster.json present" : "Roster missing",
5986
6592
  "Run agent-kit init or agent-kit update"
5987
6593
  )
5988
6594
  );
@@ -6000,7 +6606,7 @@ function buildSignals(cwd, maintainerProfile) {
6000
6606
  signals.push(
6001
6607
  signal("l5-subagents", 5, "Tier-B specialist activation", tierB.pass, tierB.evidence, "Run agent-kit init --activate cursor|codex|claude|copilot")
6002
6608
  );
6003
- const loopCoding = existsSync21(join24(cwd, "LOOP_CODING.md"));
6609
+ const loopCoding = existsSync24(join27(cwd, "LOOP_CODING.md"));
6004
6610
  signals.push(
6005
6611
  signal(
6006
6612
  "l6-loop-coding",
@@ -6031,12 +6637,12 @@ function buildSignals(cwd, maintainerProfile) {
6031
6637
  )
6032
6638
  );
6033
6639
  if (maintainerProfile) {
6034
- const pkgPath = join24(cwd, "package.json");
6640
+ const pkgPath = join27(cwd, "package.json");
6035
6641
  let releaseCheck = false;
6036
- if (existsSync21(pkgPath)) {
6642
+ if (existsSync24(pkgPath)) {
6037
6643
  try {
6038
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
6039
- releaseCheck = Boolean(pkg.scripts?.["release:check"]) && existsSync21(join24(cwd, "scripts/release-check.mjs"));
6644
+ const pkg = JSON.parse(readFileSync24(pkgPath, "utf8"));
6645
+ releaseCheck = Boolean(pkg.scripts?.["release:check"]) && existsSync24(join27(cwd, "scripts/release-check.mjs"));
6040
6646
  } catch {
6041
6647
  releaseCheck = false;
6042
6648
  }
@@ -6051,7 +6657,7 @@ function buildSignals(cwd, maintainerProfile) {
6051
6657
  "Use npm run release:check before merge; see MAINTAINER_RELEASE.md"
6052
6658
  )
6053
6659
  );
6054
- const maintainerDocs = existsSync21(join24(cwd, "MAINTAINER_RELEASE.md")) || readDocSnippet(cwd, "DOCS.md", ["maintainer dogfood", "dogfood:init"]);
6660
+ const maintainerDocs = existsSync24(join27(cwd, "MAINTAINER_RELEASE.md")) || readDocSnippet(cwd, "DOCS.md", ["maintainer dogfood", "dogfood:init"]);
6055
6661
  signals.push(
6056
6662
  signal(
6057
6663
  "l6-maintainer-docs",
@@ -6079,8 +6685,8 @@ function buildSignals(cwd, maintainerProfile) {
6079
6685
  signals.push(
6080
6686
  signal("l6-adapter-validate", 6, "Adapter validate for active IDE", adapterPass, adapterEvidence, "Run agent-kit adapter validate cursor|codex|all")
6081
6687
  );
6082
- const ciWorkflow = existsSync21(join24(cwd, ".github/workflows/agent-kit-audit.yml"));
6083
- const testingEval = existsSync21(join24(cwd, "TESTING.md")) && readDocSnippet(cwd, "TESTING.md", ["agent-kit audit", "eval"]);
6688
+ const ciWorkflow = existsSync24(join27(cwd, ".github/workflows/agent-kit-audit.yml"));
6689
+ const testingEval = existsSync24(join27(cwd, "TESTING.md")) && readDocSnippet(cwd, "TESTING.md", ["agent-kit audit", "eval"]);
6084
6690
  const evalLoop = ciWorkflow || testingEval;
6085
6691
  signals.push(
6086
6692
  signal(
@@ -6161,48 +6767,138 @@ function invalidateAgenticLevelCache(cwd) {
6161
6767
  cache.delete(cwd);
6162
6768
  }
6163
6769
 
6770
+ // src/studio/local-http-security.ts
6771
+ import { randomBytes, timingSafeEqual } from "crypto";
6772
+ var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
6773
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost"]);
6774
+ var LocalHttpRequestError = class extends Error {
6775
+ statusCode;
6776
+ constructor(statusCode, message) {
6777
+ super(message);
6778
+ this.name = "LocalHttpRequestError";
6779
+ this.statusCode = statusCode;
6780
+ }
6781
+ };
6782
+ function isLoopbackHost(host) {
6783
+ return LOOPBACK_HOSTS.has(
6784
+ host.trim().toLowerCase().replace(/^\[|\]$/g, "")
6785
+ );
6786
+ }
6787
+ function createLocalHttpSecurity(host) {
6788
+ if (!isLoopbackHost(host)) {
6789
+ throw new Error(`Agent Studio only accepts loopback hosts (127.0.0.1, ::1, or localhost); received ${host}.`);
6790
+ }
6791
+ return {
6792
+ csrfToken: randomBytes(32).toString("base64url"),
6793
+ host,
6794
+ port: 0
6795
+ };
6796
+ }
6797
+ function formatLocalUrl(host, port) {
6798
+ const normalized = host.trim().replace(/^\[|\]$/g, "");
6799
+ return `http://${normalized.includes(":") ? `[${normalized}]` : normalized}:${port}`;
6800
+ }
6801
+ function parseAuthority(authority) {
6802
+ try {
6803
+ return new URL(`http://${authority}`);
6804
+ } catch {
6805
+ throw new LocalHttpRequestError(400, "Invalid Host header.");
6806
+ }
6807
+ }
6808
+ function safeTokenEqual(actual, expected) {
6809
+ const actualBuffer = Buffer.from(actual);
6810
+ const expectedBuffer = Buffer.from(expected);
6811
+ return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer);
6812
+ }
6813
+ function assertSecureLocalRequest(request, security) {
6814
+ const hostHeader = request.headers.host;
6815
+ if (!hostHeader) throw new LocalHttpRequestError(400, "Host header is required.");
6816
+ const authority = parseAuthority(hostHeader);
6817
+ if (!isLoopbackHost(authority.hostname)) {
6818
+ throw new LocalHttpRequestError(403, "Host must resolve to a loopback address.");
6819
+ }
6820
+ const requestedPort = authority.port ? Number(authority.port) : 80;
6821
+ if (security.port > 0 && requestedPort !== security.port) {
6822
+ throw new LocalHttpRequestError(403, "Host port does not match the local server.");
6823
+ }
6824
+ const method = (request.method ?? "GET").toUpperCase();
6825
+ if (!MUTATING_METHODS.has(method)) return;
6826
+ const contentType = request.headers["content-type"] ?? "";
6827
+ if (!contentType.toLowerCase().startsWith("application/json")) {
6828
+ throw new LocalHttpRequestError(415, "Mutating requests require Content-Type: application/json.");
6829
+ }
6830
+ const token = request.headers["x-agent-kit-csrf"];
6831
+ if (typeof token !== "string" || !safeTokenEqual(token, security.csrfToken)) {
6832
+ throw new LocalHttpRequestError(403, "Missing or invalid local request token.");
6833
+ }
6834
+ const fetchSite = request.headers["sec-fetch-site"];
6835
+ if (typeof fetchSite === "string" && fetchSite !== "same-origin" && fetchSite !== "none") {
6836
+ throw new LocalHttpRequestError(403, "Cross-site requests are not allowed.");
6837
+ }
6838
+ const origin = request.headers.origin;
6839
+ if (typeof origin === "string") {
6840
+ let parsedOrigin;
6841
+ try {
6842
+ parsedOrigin = new URL(origin);
6843
+ } catch {
6844
+ throw new LocalHttpRequestError(403, "Invalid Origin header.");
6845
+ }
6846
+ const originPort = parsedOrigin.port ? Number(parsedOrigin.port) : parsedOrigin.protocol === "https:" ? 443 : 80;
6847
+ if (parsedOrigin.protocol !== "http:" || !isLoopbackHost(parsedOrigin.hostname) || originPort !== security.port) {
6848
+ throw new LocalHttpRequestError(403, "Origin does not match the local server.");
6849
+ }
6850
+ }
6851
+ }
6852
+ function baseSecurityHeaders() {
6853
+ return {
6854
+ "Cache-Control": "no-store",
6855
+ "Cross-Origin-Opener-Policy": "same-origin",
6856
+ "Cross-Origin-Resource-Policy": "same-origin",
6857
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=()",
6858
+ "Referrer-Policy": "no-referrer",
6859
+ "X-Content-Type-Options": "nosniff",
6860
+ "X-Frame-Options": "DENY"
6861
+ };
6862
+ }
6863
+ function secureLocalHtml(html, security) {
6864
+ const nonce = randomBytes(18).toString("base64url");
6865
+ const meta = `<meta name="agent-kit-csrf-token" content="${security.csrfToken}">`;
6866
+ const body = html.replace("</head>", ` ${meta}
6867
+ </head>`).replace(/<style>/g, `<style nonce="${nonce}">`).replace(/<script>/g, `<script nonce="${nonce}">`);
6868
+ const csp = [
6869
+ "default-src 'none'",
6870
+ "base-uri 'none'",
6871
+ "connect-src 'self'",
6872
+ "form-action 'self'",
6873
+ "frame-ancestors 'none'",
6874
+ "img-src data:",
6875
+ "object-src 'none'",
6876
+ `script-src 'nonce-${nonce}'`,
6877
+ `style-src 'nonce-${nonce}'`,
6878
+ "style-src-attr 'unsafe-inline'"
6879
+ ].join("; ");
6880
+ return { body, csp };
6881
+ }
6882
+
6164
6883
  // src/studio/setup-server.ts
6165
6884
  var DEFAULT_PORT = 9321;
6166
6885
  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
6886
  function sendJson(response, statusCode, payload) {
6193
6887
  response.writeHead(statusCode, {
6888
+ ...baseSecurityHeaders(),
6194
6889
  "Content-Type": "application/json; charset=utf-8",
6195
- "Cache-Control": "no-store"
6890
+ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'"
6196
6891
  });
6197
6892
  response.end(JSON.stringify(payload));
6198
6893
  }
6199
- function sendHtml(response, html) {
6894
+ function sendHtml(response, html, security) {
6895
+ const secured = secureLocalHtml(html, security);
6200
6896
  response.writeHead(200, {
6897
+ ...baseSecurityHeaders(),
6201
6898
  "Content-Type": "text/html; charset=utf-8",
6202
- "Cache-Control": "no-store",
6203
- "Content-Security-Policy": "default-src 'none'; connect-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'self'"
6899
+ "Content-Security-Policy": secured.csp
6204
6900
  });
6205
- response.end(html);
6901
+ response.end(secured.body);
6206
6902
  }
6207
6903
  function buildStatePayload(cwd, options = {}) {
6208
6904
  ensureProjectContextForSetup(cwd);
@@ -6231,7 +6927,7 @@ function buildStatePayload(cwd, options = {}) {
6231
6927
  };
6232
6928
  }
6233
6929
  function sendRedirect(response, location) {
6234
- response.writeHead(302, { Location: location, "Cache-Control": "no-store" });
6930
+ response.writeHead(302, { ...baseSecurityHeaders(), Location: location });
6235
6931
  response.end();
6236
6932
  }
6237
6933
  function findOfficeStation(cwd, stationId) {
@@ -6250,18 +6946,27 @@ function markOfficeSectionComplete(cwd, stationId, form) {
6250
6946
  }
6251
6947
  markSectionComplete(cwd, section);
6252
6948
  }
6253
- async function handleRequest(cwd, request, response) {
6949
+ async function handleRequest(cwd, request, response, security) {
6950
+ try {
6951
+ assertSecureLocalRequest(request, security);
6952
+ } catch (error) {
6953
+ if (error instanceof LocalHttpRequestError) {
6954
+ sendJson(response, error.statusCode, { error: error.message });
6955
+ return;
6956
+ }
6957
+ throw error;
6958
+ }
6254
6959
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
6255
6960
  if (request.method === "GET" && url.pathname === "/setup/wizard") {
6256
6961
  sendRedirect(response, "/wizard");
6257
6962
  return;
6258
6963
  }
6259
6964
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office" || url.pathname === "/setup")) {
6260
- sendHtml(response, renderSetupOfficeHtmlWithContext(cwd));
6965
+ sendHtml(response, renderSetupOfficeHtmlWithContext(cwd), security);
6261
6966
  return;
6262
6967
  }
6263
6968
  if (request.method === "GET" && url.pathname === "/wizard") {
6264
- sendHtml(response, renderSetupWizardHtmlWithContext(cwd));
6969
+ sendHtml(response, renderSetupWizardHtmlWithContext(cwd), security);
6265
6970
  return;
6266
6971
  }
6267
6972
  if (request.method === "GET" && url.pathname === "/api/state") {
@@ -6276,6 +6981,9 @@ async function handleRequest(cwd, request, response) {
6276
6981
  try {
6277
6982
  const body = await readJsonBody(request);
6278
6983
  if (typeof body.completeSection === "string") {
6984
+ if (!(body.completeSection in SECTION_LABELS)) {
6985
+ throw new Error("Invalid section id.");
6986
+ }
6279
6987
  markSectionComplete(cwd, body.completeSection);
6280
6988
  }
6281
6989
  const patch = {};
@@ -6427,7 +7135,7 @@ async function handleRequest(cwd, request, response) {
6427
7135
  sendJson(response, 404, { error: "Not found." });
6428
7136
  }
6429
7137
  function listen(server, host, port) {
6430
- return new Promise((resolve3, reject) => {
7138
+ return new Promise((resolve4, reject) => {
6431
7139
  server.once("error", reject);
6432
7140
  server.listen(port, host, () => {
6433
7141
  const address = server.address();
@@ -6435,17 +7143,18 @@ function listen(server, host, port) {
6435
7143
  reject(new Error("Could not determine setup server port."));
6436
7144
  return;
6437
7145
  }
6438
- resolve3(address.port);
7146
+ resolve4(address.port);
6439
7147
  });
6440
7148
  });
6441
7149
  }
6442
7150
  async function startSetupServer(options) {
6443
7151
  const host = options.host ?? DEFAULT_HOST;
7152
+ const security = createLocalHttpSecurity(host);
6444
7153
  const requestedPort = options.port ?? DEFAULT_PORT;
6445
7154
  ensureProjectContextForSetup(options.cwd);
6446
7155
  loadOnboardingState(options.cwd);
6447
7156
  const server = createServer((request, response) => {
6448
- handleRequest(options.cwd, request, response).catch((error) => {
7157
+ handleRequest(options.cwd, request, response, security).catch((error) => {
6449
7158
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
6450
7159
  });
6451
7160
  });
@@ -6461,16 +7170,18 @@ async function startSetupServer(options) {
6461
7170
  throw error;
6462
7171
  }
6463
7172
  }
7173
+ security.port = port;
6464
7174
  return {
6465
- url: `http://${host}:${port}`,
7175
+ url: formatLocalUrl(host, port),
6466
7176
  port,
6467
7177
  requestedPort,
6468
7178
  portFallback,
6469
7179
  defaultView: "office",
6470
- close: () => new Promise((resolve3, reject) => {
7180
+ csrfToken: security.csrfToken,
7181
+ close: () => new Promise((resolve4, reject) => {
6471
7182
  server.close((closeError) => {
6472
7183
  if (closeError) reject(closeError);
6473
- else resolve3();
7184
+ else resolve4();
6474
7185
  });
6475
7186
  })
6476
7187
  };
@@ -6479,74 +7190,135 @@ async function startSetupServer(options) {
6479
7190
  // src/studio/studio-server.ts
6480
7191
  import { watch } from "fs";
6481
7192
  import { createServer as createServer2 } from "http";
6482
- import { join as join25 } from "path";
7193
+ import { join as join28 } from "path";
6483
7194
  var DEFAULT_PORT2 = 9331;
6484
7195
  var DEFAULT_HOST2 = "127.0.0.1";
6485
- var sseClients = /* @__PURE__ */ new Set();
6486
- var activeWatcher = null;
6487
- var watchedEventsPath = null;
7196
+ var StudioEventHub = class {
7197
+ constructor(cwd) {
7198
+ this.cwd = cwd;
7199
+ }
7200
+ cwd;
7201
+ streams = /* @__PURE__ */ new Map();
7202
+ subscribe(sessionId, response) {
7203
+ const stream = this.getOrCreate(sessionId);
7204
+ stream.clients.add(response);
7205
+ this.watch(sessionId, stream);
7206
+ return () => {
7207
+ stream.clients.delete(response);
7208
+ if (stream.clients.size === 0) {
7209
+ stream.watcher?.close();
7210
+ this.streams.delete(sessionId);
7211
+ }
7212
+ };
7213
+ }
7214
+ broadcast(sessionId, event, total) {
7215
+ const stream = this.streams.get(sessionId);
7216
+ if (!stream) return;
7217
+ const eventRecord = event && typeof event === "object" ? event : {};
7218
+ const eventKey = eventRecord.eventId ?? eventRecord.sequence;
7219
+ stream.lastEventKey = typeof eventKey === "string" || typeof eventKey === "number" ? String(eventKey) : String(total);
7220
+ const payload = `event: event
7221
+ data: ${JSON.stringify({ sessionId, event, total })}
7222
+
7223
+ `;
7224
+ for (const client of stream.clients) {
7225
+ try {
7226
+ client.write(payload);
7227
+ } catch {
7228
+ stream.clients.delete(client);
7229
+ }
7230
+ }
7231
+ }
7232
+ close() {
7233
+ for (const stream of this.streams.values()) {
7234
+ stream.watcher?.close();
7235
+ for (const client of stream.clients) {
7236
+ try {
7237
+ client.end();
7238
+ } catch {
7239
+ }
7240
+ }
7241
+ }
7242
+ this.streams.clear();
7243
+ }
7244
+ getOrCreate(sessionId) {
7245
+ const existing = this.streams.get(sessionId);
7246
+ if (existing) return existing;
7247
+ const stream = { clients: /* @__PURE__ */ new Set(), watcher: null, lastEventKey: "" };
7248
+ this.streams.set(sessionId, stream);
7249
+ return stream;
7250
+ }
7251
+ watch(sessionId, stream) {
7252
+ if (stream.watcher) return;
7253
+ const eventsPath2 = join28(this.cwd, COUNCIL_SESSIONS_DIR, sessionId, "events.jsonl");
7254
+ try {
7255
+ stream.watcher = watch(eventsPath2, () => {
7256
+ try {
7257
+ const events = readSessionEvents(this.cwd, sessionId);
7258
+ const latest = events.at(-1);
7259
+ if (!latest) return;
7260
+ const eventKey = String(latest.eventId ?? latest.sequence ?? events.length);
7261
+ if (eventKey === stream.lastEventKey) return;
7262
+ this.broadcast(sessionId, latest, events.length);
7263
+ } catch {
7264
+ }
7265
+ });
7266
+ } catch {
7267
+ stream.watcher = null;
7268
+ }
7269
+ }
7270
+ };
6488
7271
  function sendJson2(response, statusCode, payload) {
6489
7272
  response.writeHead(statusCode, {
7273
+ ...baseSecurityHeaders(),
6490
7274
  "Content-Type": "application/json; charset=utf-8",
6491
- "Cache-Control": "no-store"
7275
+ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'"
6492
7276
  });
6493
7277
  response.end(JSON.stringify(payload));
6494
7278
  }
6495
- function sendHtml2(response, html) {
7279
+ function sendHtml2(response, html, security) {
7280
+ const secured = secureLocalHtml(html, security);
6496
7281
  response.writeHead(200, {
7282
+ ...baseSecurityHeaders(),
6497
7283
  "Content-Type": "text/html; charset=utf-8",
6498
- "Cache-Control": "no-store",
6499
- "Content-Security-Policy": "default-src 'none'; connect-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'self'"
7284
+ "Content-Security-Policy": secured.csp
6500
7285
  });
6501
- response.end(html);
7286
+ response.end(secured.body);
6502
7287
  }
6503
- function broadcastSse(event, data) {
6504
- const payload = `event: ${event}
6505
- data: ${JSON.stringify(data)}
6506
-
6507
- `;
6508
- for (const client of sseClients) {
6509
- try {
6510
- client.write(payload);
6511
- } catch {
6512
- sseClients.delete(client);
7288
+ function safeSessionId(raw) {
7289
+ if (!/^[a-z0-9-]+$/i.test(raw)) return null;
7290
+ return raw;
7291
+ }
7292
+ async function loadRuntimeService(cwd) {
7293
+ try {
7294
+ const { AgentKitRuntimeService } = await import("@appsforgood/agent-kit-runtime");
7295
+ return new AgentKitRuntimeService(cwd);
7296
+ } catch (error) {
7297
+ const message = error instanceof Error ? error.message : String(error);
7298
+ if (message.includes("@appsforgood/agent-kit-runtime") || message.includes("Cannot find package")) {
7299
+ throw new Error("The optional orchestrator runtime is not installed.");
6513
7300
  }
7301
+ throw error;
6514
7302
  }
6515
7303
  }
6516
- function stopWatcher() {
6517
- if (activeWatcher) {
6518
- activeWatcher.close();
6519
- activeWatcher = null;
6520
- watchedEventsPath = null;
6521
- }
7304
+ function handleRequest2(cwd, request, response, security, eventHub) {
7305
+ handleRequestAsync(cwd, request, response, security, eventHub).catch((error) => {
7306
+ sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
7307
+ });
6522
7308
  }
6523
- function watchSessionEvents(cwd, sessionId) {
6524
- const eventsPath2 = join25(cwd, COUNCIL_SESSIONS_DIR, sessionId, "events.jsonl");
6525
- if (watchedEventsPath === eventsPath2 && activeWatcher) return;
6526
- stopWatcher();
6527
- watchedEventsPath = eventsPath2;
7309
+ async function handleRequestAsync(cwd, request, response, security, eventHub) {
6528
7310
  try {
6529
- activeWatcher = watch(eventsPath2, () => {
6530
- try {
6531
- const events = readSessionEvents(cwd, sessionId);
6532
- const latest = events.at(-1);
6533
- if (latest) broadcastSse("event", { sessionId, event: latest, total: events.length });
6534
- } catch {
6535
- }
6536
- });
6537
- } catch {
6538
- watchedEventsPath = null;
6539
- activeWatcher = null;
7311
+ assertSecureLocalRequest(request, security);
7312
+ } catch (error) {
7313
+ if (error instanceof LocalHttpRequestError) {
7314
+ sendJson2(response, error.statusCode, { error: error.message });
7315
+ return;
7316
+ }
7317
+ throw error;
6540
7318
  }
6541
- }
6542
- function safeSessionId(raw) {
6543
- if (!/^[a-z0-9-]+$/i.test(raw)) return null;
6544
- return raw;
6545
- }
6546
- function handleRequest2(cwd, request, response) {
6547
7319
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
6548
7320
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office")) {
6549
- sendHtml2(response, renderLiveStudioHtmlWithContext(cwd));
7321
+ sendHtml2(response, renderLiveStudioHtmlWithContext(cwd), security);
6550
7322
  return;
6551
7323
  }
6552
7324
  if (request.method === "GET" && url.pathname === "/api/sessions") {
@@ -6561,6 +7333,121 @@ function handleRequest2(cwd, request, response) {
6561
7333
  sendJson2(response, 200, { activeSessionId, sessions });
6562
7334
  return;
6563
7335
  }
7336
+ if (request.method === "GET" && url.pathname === "/api/runtime/runs") {
7337
+ try {
7338
+ const runtime = await loadRuntimeService(cwd);
7339
+ const validation = runtime.validate();
7340
+ sendJson2(response, 200, { runs: runtime.status(), enabled: validation.enabled, warnings: validation.warnings });
7341
+ } catch (error) {
7342
+ sendJson2(response, 503, { error: error instanceof Error ? error.message : String(error) });
7343
+ }
7344
+ return;
7345
+ }
7346
+ if (request.method === "POST" && url.pathname === "/api/runtime/plan") {
7347
+ try {
7348
+ const body = await readJsonBody(request);
7349
+ const goal = typeof body.goal === "string" ? body.goal.trim() : "";
7350
+ const workflowId = typeof body.workflowId === "string" && body.workflowId.trim() ? body.workflowId.trim() : void 0;
7351
+ if (!goal || goal.length > 2e4) {
7352
+ sendJson2(response, 400, { error: "goal must be between 1 and 20000 characters." });
7353
+ return;
7354
+ }
7355
+ const runtime = await loadRuntimeService(cwd);
7356
+ sendJson2(response, 200, { plan: runtime.plan(goal, workflowId) });
7357
+ } catch (error) {
7358
+ sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
7359
+ }
7360
+ return;
7361
+ }
7362
+ if (request.method === "POST" && url.pathname === "/api/runtime/runs") {
7363
+ try {
7364
+ const body = await readJsonBody(request);
7365
+ const goal = typeof body.goal === "string" ? body.goal.trim() : "";
7366
+ const workflowId = typeof body.workflowId === "string" && body.workflowId.trim() ? body.workflowId.trim() : void 0;
7367
+ if (!goal || goal.length > 2e4) {
7368
+ sendJson2(response, 400, { error: "goal must be between 1 and 20000 characters." });
7369
+ return;
7370
+ }
7371
+ if (body.acknowledgeDirtyBase !== void 0 && typeof body.acknowledgeDirtyBase !== "boolean") {
7372
+ sendJson2(response, 400, { error: "acknowledgeDirtyBase must be boolean." });
7373
+ return;
7374
+ }
7375
+ const runtime = await loadRuntimeService(cwd);
7376
+ const run = await runtime.run(goal, {
7377
+ ...workflowId ? { workflowId } : {},
7378
+ ...body.acknowledgeDirtyBase === true ? { acknowledgeDirtyBase: true } : {}
7379
+ });
7380
+ sendJson2(response, 201, { run, events: runtime.events.events(run.runId) });
7381
+ } catch (error) {
7382
+ sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
7383
+ }
7384
+ return;
7385
+ }
7386
+ const runtimeRunMatch = url.pathname.match(/^\/api\/runtime\/runs\/([^/]+)$/);
7387
+ if (request.method === "GET" && runtimeRunMatch) {
7388
+ const runId = safeSessionId(runtimeRunMatch[1] ?? "");
7389
+ if (!runId) {
7390
+ sendJson2(response, 400, { error: "Invalid run id." });
7391
+ return;
7392
+ }
7393
+ try {
7394
+ const runtime = await loadRuntimeService(cwd);
7395
+ const run = runtime.status(runId);
7396
+ sendJson2(response, 200, { run, events: runtime.events.events(runId) });
7397
+ } catch (error) {
7398
+ sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
7399
+ }
7400
+ return;
7401
+ }
7402
+ const runtimeDecisionMatch = url.pathname.match(/^\/api\/runtime\/runs\/([^/]+)\/decision$/);
7403
+ if (request.method === "POST" && runtimeDecisionMatch) {
7404
+ const runId = safeSessionId(runtimeDecisionMatch[1] ?? "");
7405
+ if (!runId) {
7406
+ sendJson2(response, 400, { error: "Invalid run id." });
7407
+ return;
7408
+ }
7409
+ try {
7410
+ const body = await readJsonBody(request);
7411
+ if (body.decision !== "approve" && body.decision !== "reject") {
7412
+ sendJson2(response, 400, { error: "decision must be approve or reject." });
7413
+ return;
7414
+ }
7415
+ const actor = typeof body.actor === "string" && body.actor.trim() ? body.actor.trim().slice(0, 200) : "studio-operator";
7416
+ const note = typeof body.note === "string" && body.note.trim() ? body.note.trim().slice(0, 2e3) : void 0;
7417
+ const runtime = await loadRuntimeService(cwd);
7418
+ const current = runtime.status(runId);
7419
+ if (Array.isArray(current) || !current.pendingApproval) {
7420
+ sendJson2(response, 409, { error: `Run ${runId} has no pending approval.` });
7421
+ return;
7422
+ }
7423
+ const run = await runtime.resume(runId, {
7424
+ approvalId: current.pendingApproval.approvalId,
7425
+ decision: body.decision,
7426
+ actor,
7427
+ ...note ? { note } : {}
7428
+ });
7429
+ sendJson2(response, 200, { run, events: runtime.events.events(runId) });
7430
+ } catch (error) {
7431
+ sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
7432
+ }
7433
+ return;
7434
+ }
7435
+ const runtimeCancelMatch = url.pathname.match(/^\/api\/runtime\/runs\/([^/]+)\/cancel$/);
7436
+ if (request.method === "POST" && runtimeCancelMatch) {
7437
+ const runId = safeSessionId(runtimeCancelMatch[1] ?? "");
7438
+ if (!runId) {
7439
+ sendJson2(response, 400, { error: "Invalid run id." });
7440
+ return;
7441
+ }
7442
+ try {
7443
+ const runtime = await loadRuntimeService(cwd);
7444
+ const run = runtime.cancel(runId);
7445
+ sendJson2(response, 200, { run, events: runtime.events.events(runId) });
7446
+ } catch (error) {
7447
+ sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
7448
+ }
7449
+ return;
7450
+ }
6564
7451
  const eventsMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/events$/);
6565
7452
  if (request.method === "GET" && eventsMatch) {
6566
7453
  const sessionId = safeSessionId(eventsMatch[1] ?? "");
@@ -6593,13 +7480,14 @@ function handleRequest2(cwd, request, response) {
6593
7480
  return;
6594
7481
  }
6595
7482
  response.writeHead(200, {
7483
+ ...baseSecurityHeaders(),
6596
7484
  "Content-Type": "text/event-stream; charset=utf-8",
6597
- "Cache-Control": "no-store",
6598
- Connection: "keep-alive"
7485
+ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'",
7486
+ Connection: "keep-alive",
7487
+ "X-Accel-Buffering": "no"
6599
7488
  });
6600
7489
  response.write(": connected\n\n");
6601
- sseClients.add(response);
6602
- watchSessionEvents(cwd, activeId);
7490
+ const unsubscribe = eventHub.subscribe(activeId, response);
6603
7491
  try {
6604
7492
  const events = readSessionEvents(cwd, activeId);
6605
7493
  response.write(`event: snapshot
@@ -6613,15 +7501,65 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
6613
7501
  `);
6614
7502
  }
6615
7503
  request.on("close", () => {
6616
- sseClients.delete(response);
6617
- if (sseClients.size === 0) stopWatcher();
7504
+ unsubscribe();
6618
7505
  });
6619
7506
  return;
6620
7507
  }
7508
+ const noteMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/note$/);
7509
+ if (request.method === "POST" && noteMatch) {
7510
+ const sessionId = safeSessionId(noteMatch[1] ?? "");
7511
+ if (!sessionId) {
7512
+ sendJson2(response, 400, { error: "Invalid session id." });
7513
+ return;
7514
+ }
7515
+ try {
7516
+ const body = await readJsonBody(request);
7517
+ const agent = typeof body.agent === "string" ? body.agent.trim() : "";
7518
+ const text = typeof body.text === "string" ? body.text.trim() : "";
7519
+ if (!agent) {
7520
+ sendJson2(response, 400, { error: "agent is required." });
7521
+ return;
7522
+ }
7523
+ if (!text) {
7524
+ sendJson2(response, 400, { error: "text is required." });
7525
+ return;
7526
+ }
7527
+ if (text.length >= 4e3) {
7528
+ sendJson2(response, 400, { error: "text must be under 4000 characters." });
7529
+ return;
7530
+ }
7531
+ const event = recordSessionNote(cwd, sessionId, agent, text);
7532
+ eventHub.broadcast(sessionId, event, readSessionEvents(cwd, sessionId).length);
7533
+ sendJson2(response, 200, { event });
7534
+ } catch (error) {
7535
+ sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
7536
+ }
7537
+ return;
7538
+ }
7539
+ const renderMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/render$/);
7540
+ if (request.method === "POST" && renderMatch) {
7541
+ const sessionId = safeSessionId(renderMatch[1] ?? "");
7542
+ if (!sessionId) {
7543
+ sendJson2(response, 400, { error: "Invalid session id." });
7544
+ return;
7545
+ }
7546
+ try {
7547
+ const result = renderSession(cwd, sessionId);
7548
+ sendJson2(response, 200, {
7549
+ rendered: true,
7550
+ sessionId: result.sessionId,
7551
+ sessionPath: result.sessionPath,
7552
+ files: [`${result.sessionPath}/index.md`, `${result.sessionPath}/transcript.md`]
7553
+ });
7554
+ } catch (error) {
7555
+ sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
7556
+ }
7557
+ return;
7558
+ }
6621
7559
  sendJson2(response, 404, { error: "Not found." });
6622
7560
  }
6623
7561
  function listen2(server, host, port) {
6624
- return new Promise((resolve3, reject) => {
7562
+ return new Promise((resolve4, reject) => {
6625
7563
  server.once("error", reject);
6626
7564
  server.listen(port, host, () => {
6627
7565
  const address = server.address();
@@ -6629,17 +7567,19 @@ function listen2(server, host, port) {
6629
7567
  reject(new Error("Could not determine studio server port."));
6630
7568
  return;
6631
7569
  }
6632
- resolve3(address.port);
7570
+ resolve4(address.port);
6633
7571
  });
6634
7572
  });
6635
7573
  }
6636
7574
  async function startStudioServer(options) {
6637
7575
  const host = options.host ?? DEFAULT_HOST2;
7576
+ const security = createLocalHttpSecurity(host);
6638
7577
  const requestedPort = options.port ?? DEFAULT_PORT2;
6639
7578
  ensureStudioDirs(options.cwd);
7579
+ const eventHub = new StudioEventHub(options.cwd);
6640
7580
  const server = createServer2((request, response) => {
6641
7581
  try {
6642
- handleRequest2(options.cwd, request, response);
7582
+ handleRequest2(options.cwd, request, response, security, eventHub);
6643
7583
  } catch (error) {
6644
7584
  sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
6645
7585
  }
@@ -6656,31 +7596,26 @@ async function startStudioServer(options) {
6656
7596
  throw error;
6657
7597
  }
6658
7598
  }
7599
+ security.port = port;
6659
7600
  return {
6660
- url: `http://${host}:${port}`,
7601
+ url: formatLocalUrl(host, port),
6661
7602
  port,
6662
7603
  requestedPort,
6663
7604
  portFallback,
6664
- close: () => new Promise((resolve3, reject) => {
6665
- stopWatcher();
6666
- for (const client of sseClients) {
6667
- try {
6668
- client.end();
6669
- } catch {
6670
- }
6671
- }
6672
- sseClients.clear();
7605
+ csrfToken: security.csrfToken,
7606
+ close: () => new Promise((resolve4, reject) => {
7607
+ eventHub.close();
6673
7608
  server.close((closeError) => {
6674
7609
  if (closeError) reject(closeError);
6675
- else resolve3();
7610
+ else resolve4();
6676
7611
  });
6677
7612
  })
6678
7613
  };
6679
7614
  }
6680
7615
 
6681
7616
  // src/studio/session-checkpoint.ts
6682
- import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
6683
- import { extname, join as join26 } from "path";
7617
+ import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
7618
+ import { extname, join as join29 } from "path";
6684
7619
  function parseCheckpointMarkdown(content) {
6685
7620
  const payload = { notes: [], decisions: [], handoffs: [], outputs: [] };
6686
7621
  const sections = content.split(/^## /m).slice(1);
@@ -6736,7 +7671,7 @@ function parseCheckpointMarkdown(content) {
6736
7671
  return payload;
6737
7672
  }
6738
7673
  function parseCheckpointFile(filePath) {
6739
- const content = readFileSync23(filePath, "utf8");
7674
+ const content = readFileSync25(filePath, "utf8");
6740
7675
  const ext = extname(filePath).toLowerCase();
6741
7676
  if (ext === ".json") {
6742
7677
  const parsed = JSON.parse(content);
@@ -6812,8 +7747,8 @@ function applySessionCheckpoint(cwd, payload) {
6812
7747
  };
6813
7748
  }
6814
7749
  function checkpointSessionFromFile(cwd, filePath) {
6815
- const absolute = join26(cwd, filePath);
6816
- if (!existsSync22(absolute)) throw new Error(`Checkpoint file not found: ${filePath}`);
7750
+ const absolute = join29(cwd, filePath);
7751
+ if (!existsSync25(absolute)) throw new Error(`Checkpoint file not found: ${filePath}`);
6817
7752
  return applySessionCheckpoint(cwd, parseCheckpointFile(absolute));
6818
7753
  }
6819
7754
 
@@ -6867,6 +7802,41 @@ var requiredOutputStatuses = ["missing", "partial", "complete", "not-applicable"
6867
7802
  function isRequiredOutputStatus(value) {
6868
7803
  return requiredOutputStatuses.includes(value);
6869
7804
  }
7805
+ async function loadRuntimeService2() {
7806
+ try {
7807
+ const { AgentKitRuntimeService } = await import("@appsforgood/agent-kit-runtime");
7808
+ return new AgentKitRuntimeService(process.cwd());
7809
+ } catch (error) {
7810
+ const message = error instanceof Error ? error.message : String(error);
7811
+ if (message.includes("@appsforgood/agent-kit-runtime") || message.includes("Cannot find package")) {
7812
+ throw new Error("The optional orchestrator runtime is not installed. Run: npm install --save-dev @appsforgood/agent-kit-runtime");
7813
+ }
7814
+ throw error;
7815
+ }
7816
+ }
7817
+ function printRuntimeRecord(record) {
7818
+ line(`${style.bold(record.runId)} ${record.status}`);
7819
+ line(`workflow: ${record.workflowId}`);
7820
+ if (record.branchName) line(`branch: ${record.branchName}`);
7821
+ if (record.commit) line(`commit: ${record.commit}`);
7822
+ if (record.pendingApproval) {
7823
+ line(`approval: ${record.pendingApproval.approvalId} (${record.pendingApproval.risk})`);
7824
+ detail(record.pendingApproval.title);
7825
+ }
7826
+ if (record.error) line(style.fail(`failure: ${record.error}`));
7827
+ }
7828
+ async function readCredentialValue() {
7829
+ if (!process.stdin.isTTY) {
7830
+ const value2 = readFileSync26(0, "utf8").replace(/[\r\n]+$/, "");
7831
+ if (!value2) throw new Error("Credential value was empty.");
7832
+ return value2;
7833
+ }
7834
+ const clack = await import("@clack/prompts");
7835
+ const value = await clack.password({ message: "Credential value" });
7836
+ if (clack.isCancel(value)) throw new Error("Credential entry cancelled.");
7837
+ if (typeof value !== "string" || !value) throw new Error("Credential value was empty.");
7838
+ return value;
7839
+ }
6870
7840
  program.name("agent-kit").description("Next.js + Supabase agent, skill, docs, design, and research kit.").version(PACKAGE_VERSION);
6871
7841
  async function runGuidedContextPrompts(cwd) {
6872
7842
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
@@ -6888,11 +7858,11 @@ async function runGuidedContextPrompts(cwd) {
6888
7858
  if (typeof answer === "string" && answer.trim()) answers[question.key] = answer.trim();
6889
7859
  }
6890
7860
  if (Object.keys(answers).length > 0) {
6891
- const contextPath = join27(cwd, ".agent-kit", "project-context.json");
6892
- if (existsSync23(contextPath)) {
6893
- const context2 = JSON.parse(readFileSync24(contextPath, "utf8"));
7861
+ const contextPath = join30(cwd, ".agent-kit", "project-context.json");
7862
+ if (existsSync26(contextPath)) {
7863
+ const context2 = JSON.parse(readFileSync26(contextPath, "utf8"));
6894
7864
  Object.assign(context2, answers);
6895
- writeFileSync2(contextPath, `${JSON.stringify(context2, null, 2)}
7865
+ writeFileSync3(contextPath, `${JSON.stringify(context2, null, 2)}
6896
7866
  `);
6897
7867
  renderProjectContext(cwd);
6898
7868
  }
@@ -6913,9 +7883,9 @@ async function runSetupServer(options) {
6913
7883
  console.log(`Pixel office (default): ${handle.url}/ | Form fallback: ${handle.url}/wizard`);
6914
7884
  console.log("Pick Quick, Standard, or Complete on first visit. Press Ctrl+C to stop.");
6915
7885
  if (options.open) void openBrowser(`${handle.url}/`);
6916
- await new Promise((resolve3) => {
7886
+ await new Promise((resolve4) => {
6917
7887
  const shutdown = () => {
6918
- void handle.close().finally(resolve3);
7888
+ void handle.close().finally(resolve4);
6919
7889
  };
6920
7890
  process.once("SIGINT", shutdown);
6921
7891
  process.once("SIGTERM", shutdown);
@@ -6980,8 +7950,20 @@ program.command("init").description("Install agent-kit docs and library files in
6980
7950
  await runSetupServer({ port: 9321, host: "127.0.0.1", open: Boolean(options.open) });
6981
7951
  }
6982
7952
  });
6983
- program.command("audit").description("Audit an existing project for agent-kit coverage gaps.").option("--json", "Print machine-readable JSON output.").option("--min-readiness <level>", `Exit non-zero unless readiness is at least this level: ${READINESS_ORDER.join(", ")}.`).action((options) => {
6984
- const report2 = createAuditReport(process.cwd());
7953
+ program.command("audit").description("Audit an existing project for agent-kit coverage gaps.").option("--json", "Print machine-readable JSON output.").option("--schema-version <version>", "JSON audit schema version: 1 or 2.", "1").option("--format <format>", "Output format: human, json, or sarif.").option("--min-readiness <level>", `Exit non-zero unless readiness is at least this level: ${READINESS_ORDER.join(", ")}.`).action((options) => {
7954
+ if (options.schemaVersion !== "1" && options.schemaVersion !== "2") {
7955
+ fail(`Invalid --schema-version value "${options.schemaVersion}". Expected 1 or 2.`);
7956
+ process.exitCode = 1;
7957
+ return;
7958
+ }
7959
+ const format = options.format ?? (options.json ? "json" : "human");
7960
+ if (!(/* @__PURE__ */ new Set(["human", "json", "sarif"])).has(format)) {
7961
+ fail(`Invalid --format value "${format}". Expected human, json, or sarif.`);
7962
+ process.exitCode = 1;
7963
+ return;
7964
+ }
7965
+ const reportV2 = options.schemaVersion === "2" || format === "sarif" ? createAuditReportV2(process.cwd()) : void 0;
7966
+ const report2 = reportV2 ?? createAuditReport(process.cwd());
6985
7967
  let minimumReadiness;
6986
7968
  if (options.minReadiness) {
6987
7969
  if (!isAuditReadinessLevel(options.minReadiness)) {
@@ -6991,7 +7973,9 @@ program.command("audit").description("Audit an existing project for agent-kit co
6991
7973
  }
6992
7974
  minimumReadiness = options.minReadiness;
6993
7975
  }
6994
- if (options.json) {
7976
+ if (format === "sarif") {
7977
+ printJson(auditReportToSarif(reportV2));
7978
+ } else if (format === "json") {
6995
7979
  printJson(report2);
6996
7980
  } else {
6997
7981
  const readinessStyle = report2.summary.fail > 0 ? style.fail : report2.summary.warn > 0 ? style.warn : style.pass;
@@ -7343,6 +8327,122 @@ correction.command("propose-upstream <id>").description("Create an upstream prop
7343
8327
  if (options.json) printJson(result);
7344
8328
  else line(`Created upstream proposal from correction ${id}.`);
7345
8329
  });
8330
+ var orchestrate = program.command("orchestrate").description("Run checkpointed council workflows with the optional local runtime.");
8331
+ orchestrate.command("validate").description("Validate orchestrator config, roster references, bounds, and credential references without calling providers.").option("--json", "Print machine-readable JSON output.").action(async (options) => {
8332
+ const result = (await loadRuntimeService2()).validate();
8333
+ if (options.json) printJson(result);
8334
+ else {
8335
+ heading("agent-kit orchestrator");
8336
+ line(`status: ${result.valid ? "valid" : "invalid"}`);
8337
+ line(`enabled: ${result.enabled}`);
8338
+ line(`roster: ${result.rosterId}`);
8339
+ line(`workflows: ${result.workflows.join(", ")}`);
8340
+ line(`providers: ${result.providers.join(", ") || "none"}`);
8341
+ for (const warning of result.warnings) detail(`warning: ${warning}`);
8342
+ }
8343
+ });
8344
+ orchestrate.command("plan <goal...>").description("Compile a deterministic offline workflow plan from the installed roster.").option("--workflow <workflow>", "Use an explicit roster workflow id.").option("--json", "Print machine-readable JSON output.").action(async (goalParts, options) => {
8345
+ const result = (await loadRuntimeService2()).plan(goalParts.join(" "), options.workflow);
8346
+ if (options.json) printJson(result);
8347
+ else {
8348
+ heading(`Workflow: ${result.workflowId}`);
8349
+ line(`sequence: ${result.sequence.join(" -> ")}`);
8350
+ line(`council: ${result.council.join(", ")}`);
8351
+ line(`approvals: ${result.approvals.join(", ") || "none"}`);
8352
+ line(`model aliases: ${result.modelAliases.join(", ") || "none"}`);
8353
+ line(`MCP servers: ${result.mcpServers.join(", ") || "none"}`);
8354
+ }
8355
+ });
8356
+ orchestrate.command("run <goal...>").description("Start a foreground checkpointed workflow in an isolated Git worktree.").option("--workflow <workflow>", "Use an explicit roster workflow id.").option("--acknowledge-dirty-base", "Acknowledge that current uncommitted changes will be excluded from the worktree.").option("--json", "Print machine-readable JSON output.").action(async (goalParts, options) => {
8357
+ const result = await (await loadRuntimeService2()).run(goalParts.join(" "), {
8358
+ ...options.workflow ? { workflowId: options.workflow } : {},
8359
+ ...options.acknowledgeDirtyBase ? { acknowledgeDirtyBase: true } : {}
8360
+ });
8361
+ if (options.json) printJson(result);
8362
+ else printRuntimeRecord(result);
8363
+ });
8364
+ orchestrate.command("status [run-id]").description("Show one runtime run or list recent runs.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
8365
+ const result = (await loadRuntimeService2()).status(runId);
8366
+ if (options.json) printJson(result);
8367
+ else if (Array.isArray(result)) {
8368
+ if (result.length === 0) line("No runtime runs found.");
8369
+ for (const record of result) printRuntimeRecord(record);
8370
+ } else {
8371
+ printRuntimeRecord(result);
8372
+ }
8373
+ });
8374
+ orchestrate.command("approve <run-id>").description("Approve the pending gate and resume the checkpointed workflow.").option("--actor <actor>", "Decision actor.", process.env.USER ?? "operator").option("--note <note>", "Decision note.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
8375
+ const runtime = await loadRuntimeService2();
8376
+ const record = runtime.status(runId);
8377
+ if (Array.isArray(record) || !record.pendingApproval) throw new Error(`Run ${runId} has no pending approval.`);
8378
+ const result = await runtime.resume(runId, {
8379
+ approvalId: record.pendingApproval.approvalId,
8380
+ decision: "approve",
8381
+ actor: options.actor,
8382
+ ...options.note ? { note: options.note } : {}
8383
+ });
8384
+ if (options.json) printJson(result);
8385
+ else printRuntimeRecord(result);
8386
+ });
8387
+ orchestrate.command("resume <run-id>").description("Resume a pending gate with an explicit approve or reject decision.").requiredOption("--decision <decision>", "approve or reject.").option("--actor <actor>", "Decision actor.", process.env.USER ?? "operator").option("--note <note>", "Decision note.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
8388
+ if (options.decision !== "approve" && options.decision !== "reject") throw new Error("--decision must be approve or reject.");
8389
+ const runtime = await loadRuntimeService2();
8390
+ const record = runtime.status(runId);
8391
+ if (Array.isArray(record) || !record.pendingApproval) throw new Error(`Run ${runId} has no pending approval.`);
8392
+ const result = await runtime.resume(runId, {
8393
+ approvalId: record.pendingApproval.approvalId,
8394
+ decision: options.decision,
8395
+ actor: options.actor,
8396
+ ...options.note ? { note: options.note } : {}
8397
+ });
8398
+ if (options.json) printJson(result);
8399
+ else printRuntimeRecord(result);
8400
+ });
8401
+ orchestrate.command("cancel <run-id>").description("Cancel a planned or approval-paused run.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
8402
+ const result = (await loadRuntimeService2()).cancel(runId);
8403
+ if (options.json) printJson(result);
8404
+ else printRuntimeRecord(result);
8405
+ });
8406
+ orchestrate.command("export <run-id>").description("Export redacted JSONL-backed runtime evidence as Markdown.").option("--output <path>", "Project-relative output path.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
8407
+ const evidence = (await loadRuntimeService2()).exportEvidence(runId);
8408
+ if (!options.output) {
8409
+ line(evidence.trimEnd());
8410
+ return;
8411
+ }
8412
+ if (isAbsolute2(options.output)) throw new Error("--output must be project-relative.");
8413
+ const root = resolve3(process.cwd());
8414
+ const output = resolve3(root, options.output);
8415
+ const relationship = relative2(root, output);
8416
+ if (relationship.startsWith("..") || isAbsolute2(relationship)) throw new Error("--output must remain inside the project.");
8417
+ mkdirSync3(dirname4(output), { recursive: true });
8418
+ writeFileSync3(output, evidence, { mode: 384 });
8419
+ if (options.json) printJson({ runId, output: relationship.replace(/\\/g, "/") });
8420
+ else line(`Exported ${runId} evidence to ${relationship}.`);
8421
+ });
8422
+ var provider = program.command("provider").description("Probe configured orchestrator model providers.");
8423
+ provider.command("probe [provider-id]").description("Probe one provider or all configured providers.").option("--json", "Print machine-readable JSON output.").action(async (providerId, options) => {
8424
+ const result = await (await loadRuntimeService2()).probeProvider(providerId);
8425
+ if (options.json) printJson(result);
8426
+ else
8427
+ for (const probe of result) line(`${probe.available ? "PASS" : "FAIL"} ${probe.providerId} ${probe.latencyMs}ms${probe.error ? `: ${probe.error}` : ""}`);
8428
+ });
8429
+ var credential = program.command("credential").description("Manage orchestrator credentials in the OS keychain.");
8430
+ credential.command("set <reference>").description("Read a credential from a masked prompt or stdin and store a keychain: reference.").action(async (reference) => {
8431
+ const value = await readCredentialValue();
8432
+ await (await loadRuntimeService2()).setCredential(reference, value);
8433
+ line(`Stored ${reference} in the OS keychain.`);
8434
+ });
8435
+ credential.command("delete <reference>").description("Delete a keychain credential reference.").option("--json", "Print machine-readable JSON output.").action(async (reference, options) => {
8436
+ const deleted = await (await loadRuntimeService2()).deleteCredential(reference);
8437
+ if (options.json) printJson({ reference, deleted });
8438
+ else line(deleted ? `Deleted ${reference}.` : `No credential existed for ${reference}.`);
8439
+ });
8440
+ var mcp = program.command("mcp").description("Probe explicitly configured MCP servers.");
8441
+ mcp.command("probe <server>").description("Connect, ping, and list allowlisted tools for one MCP server.").option("--json", "Print machine-readable JSON output.").action(async (server, options) => {
8442
+ const result = await (await loadRuntimeService2()).probeMcp(server);
8443
+ if (options.json) printJson(result);
8444
+ else printJson(result);
8445
+ });
7346
8446
  var studio = program.command("studio").description("Export and serve local Agent Studio views.");
7347
8447
  async function runStudioServer(options) {
7348
8448
  const handle = await startStudioServer({
@@ -7356,9 +8456,9 @@ async function runStudioServer(options) {
7356
8456
  console.log(`Agent Kit v${PACKAGE_VERSION} \u2014 live studio at ${handle.url}/`);
7357
8457
  console.log("SSE: GET /api/events/stream | Press Ctrl+C to stop.");
7358
8458
  if (options.open) void openBrowser(`${handle.url}/`);
7359
- await new Promise((resolve3) => {
8459
+ await new Promise((resolve4) => {
7360
8460
  const shutdown = () => {
7361
- void handle.close().finally(resolve3);
8461
+ void handle.close().finally(resolve4);
7362
8462
  };
7363
8463
  process.once("SIGINT", shutdown);
7364
8464
  process.once("SIGTERM", shutdown);