@appsforgood/next-supabase-kit 0.1.8 → 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 (48) hide show
  1. package/BEST_PRACTICE_EVIDENCE.md +1 -1
  2. package/CHANGELOG.md +9 -0
  3. package/DOGFOOD.md +2 -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/assistant-adapters/README.md +3 -0
  10. package/assistant-adapters/antigravity.md +2 -0
  11. package/assistant-adapters/claude-code-subagents.md +1 -0
  12. package/assistant-adapters/codex-agents.md +2 -0
  13. package/assistant-adapters/cursor-agent-kit.mdc +2 -0
  14. package/assistant-adapters/cursor-planner.mdc +1 -1
  15. package/assistant-adapters/orchestrator-runtime.md +39 -0
  16. package/checklists/frontend-quality.md +1 -0
  17. package/checklists/ui-detectors.md +6 -0
  18. package/dist/index.js +1723 -688
  19. package/dist/index.js.map +1 -1
  20. package/dist/studio/office/assets/office.css +138 -7
  21. package/dist/studio/office/assets/office.js +200 -2
  22. package/dist/studio/wizard/assets/wizard.js +7 -1
  23. package/examples/next-supabase-installed/.agent-kit/manifest.json +187 -14
  24. package/examples/next-supabase-installed/audit-output.json +405 -380
  25. package/package.json +25 -4
  26. package/prompts/frontend-design-review.md +6 -0
  27. package/prompts/screenshot-review.md +2 -1
  28. package/schemas/audit-report-v2.schema.json +71 -0
  29. package/schemas/audit-report.schema.json +16 -16
  30. package/schemas/orchestrator.schema.json +167 -0
  31. package/schemas/runtime-event.schema.json +66 -0
  32. package/schemas/runtime-run.schema.json +58 -0
  33. package/schemas/session-event.schema.json +2 -0
  34. package/templates/next-supabase/.agent-kit/orchestrator.json +48 -0
  35. package/templates/next-supabase/.agent-kit/runtime/gitignore.template +2 -0
  36. package/templates/next-supabase/AGENTS.md +2 -0
  37. package/templates/next-supabase/ASSISTANT_ADAPTERS.md +20 -0
  38. package/templates/next-supabase/DECISIONS.md +14 -0
  39. package/templates/next-supabase/DEPLOYMENT.md +4 -0
  40. package/templates/next-supabase/DESIGN.md +6 -0
  41. package/templates/next-supabase/DOCS.md +8 -0
  42. package/templates/next-supabase/MODEL_ROUTING.md +9 -0
  43. package/templates/next-supabase/QUALITY_GATES.md +5 -1
  44. package/templates/next-supabase/SECURITY.md +10 -0
  45. package/templates/next-supabase/SPEC.md +3 -1
  46. package/templates/next-supabase/STYLE_GUIDE.md +15 -0
  47. package/templates/next-supabase/TESTING.md +6 -0
  48. 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.8";
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
  }
@@ -363,7 +470,7 @@ function unique(values) {
363
470
  return [...new Set(values.filter(Boolean))].sort();
364
471
  }
365
472
  function readJsonBody(request) {
366
- return new Promise((resolve3, reject) => {
473
+ return new Promise((resolve4, reject) => {
367
474
  const chunks = [];
368
475
  let bodyTooLarge = false;
369
476
  request.on("data", (chunk) => {
@@ -378,11 +485,11 @@ function readJsonBody(request) {
378
485
  if (bodyTooLarge) return;
379
486
  const raw = Buffer.concat(chunks).toString("utf8").trim();
380
487
  if (!raw) {
381
- resolve3({});
488
+ resolve4({});
382
489
  return;
383
490
  }
384
491
  try {
385
- resolve3(JSON.parse(raw));
492
+ resolve4(JSON.parse(raw));
386
493
  } catch {
387
494
  reject(new Error("Request body must be valid JSON."));
388
495
  }
@@ -392,8 +499,8 @@ function readJsonBody(request) {
392
499
  }
393
500
 
394
501
  // src/install/audit.ts
395
- import { existsSync as existsSync11, readFileSync as readFileSync9, statSync } from "fs";
396
- 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";
397
504
 
398
505
  // src/config/contracts.ts
399
506
  import { z } from "zod";
@@ -608,6 +715,8 @@ var StudioSessionContract = z.object({
608
715
  renderedAt: z.string().datetime().optional()
609
716
  }).strict();
610
717
  var SessionEventContract = z.object({
718
+ eventId: z.string().uuid().optional(),
719
+ sequence: z.number().int().positive().optional(),
611
720
  type: z.enum([
612
721
  "session_started",
613
722
  "project_context_loaded",
@@ -1169,8 +1278,8 @@ function onboardingStateExists(cwd) {
1169
1278
  }
1170
1279
 
1171
1280
  // src/install/install.ts
1172
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
1173
- import { join as join10 } from "path";
1281
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
1282
+ import { join as join11 } from "path";
1174
1283
 
1175
1284
  // src/install/ide-activate.ts
1176
1285
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
@@ -1342,10 +1451,11 @@ function writeGeneratedAgentFile(cwd, relativePath, content, force, result) {
1342
1451
  result.unchanged.push(relativePath);
1343
1452
  return;
1344
1453
  }
1345
- const conflictPath = join8(cwd, ".agent-kit", "conflicts", relativePath.replace(/\//g, "__"));
1346
- ensureDir(join8(cwd, ".agent-kit", "conflicts"));
1347
- writeText(conflictPath, existing);
1348
- 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}`);
1349
1459
  return;
1350
1460
  }
1351
1461
  ensureDir(join8(cwd, relativePath.split("/").slice(0, -1).join("/")));
@@ -1613,17 +1723,57 @@ function ideSurfaceToActivateTarget(ideSurface) {
1613
1723
  return null;
1614
1724
  }
1615
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
+
1616
1765
  // src/install/install.ts
1617
1766
  function initProject(options) {
1618
1767
  const cwd = options.cwd;
1619
1768
  const stack = options.stack ?? DEFAULT_CONFIG.stack;
1620
1769
  const packageRoot = findPackageRoot();
1621
- const templateRoot = join10(packageRoot, "templates", stack);
1770
+ const templateRoot = join11(packageRoot, "templates", stack);
1771
+ const managedAssets = listManagedAssets(packageRoot, stack);
1622
1772
  if (!existsSync10(templateRoot)) {
1623
1773
  throw new Error(`Unsupported stack profile: ${stack}`);
1624
1774
  }
1625
- ensureDir(join10(cwd, ".agent-kit"));
1626
- ensureDir(join10(cwd, ".agent-kit", "conflicts"));
1775
+ ensureDir(join11(cwd, ".agent-kit"));
1776
+ ensureDir(join11(cwd, ".agent-kit", "conflicts"));
1627
1777
  const result = {
1628
1778
  copied: [],
1629
1779
  unchanged: [],
@@ -1633,11 +1783,11 @@ function initProject(options) {
1633
1783
  };
1634
1784
  const templateHashes = {};
1635
1785
  for (const doc of ROOT_DOCS) {
1636
- const templatePath = join10(templateRoot, doc);
1637
- templateHashes[doc] = sha256(readFileSync8(templatePath, "utf8"));
1786
+ const templatePath = join11(templateRoot, doc);
1787
+ templateHashes[doc] = sha256(readFileSync9(templatePath, "utf8"));
1638
1788
  const copyResult = copyTextWithConflict(templatePath, cwd, doc, {
1639
1789
  force: Boolean(options.force),
1640
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1790
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1641
1791
  });
1642
1792
  if (copyResult.action === "created") result.copied.push(copyResult.target);
1643
1793
  if (copyResult.action === "unchanged") result.unchanged.push(copyResult.target);
@@ -1646,13 +1796,18 @@ function initProject(options) {
1646
1796
  result.conflicts.push(`${copyResult.target} -> ${copyResult.conflictPath}`);
1647
1797
  }
1648
1798
  }
1649
- for (const folder of LIBRARY_FOLDERS) {
1650
- 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}`);
1651
1806
  }
1652
1807
  for (const adapter2 of CURSOR_ADAPTER_FILES) {
1653
- const adapterCopy = copyTextWithConflict(join10(packageRoot, adapter2.source), cwd, adapter2.target, {
1808
+ const adapterCopy = copyTextWithConflict(join11(packageRoot, adapter2.source), cwd, adapter2.target, {
1654
1809
  force: Boolean(options.force),
1655
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1810
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1656
1811
  });
1657
1812
  if (adapterCopy.action === "created") result.copied.push(adapterCopy.target);
1658
1813
  if (adapterCopy.action === "unchanged") result.unchanged.push(adapterCopy.target);
@@ -1661,23 +1816,40 @@ function initProject(options) {
1661
1816
  result.conflicts.push(`${adapterCopy.target} -> ${adapterCopy.conflictPath}`);
1662
1817
  }
1663
1818
  }
1664
- 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, {
1665
1820
  force: Boolean(options.force),
1666
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1821
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1667
1822
  });
1668
1823
  if (rosterCopy.action === "created") result.copied.push(rosterCopy.target);
1669
1824
  if (rosterCopy.action === "unchanged") result.unchanged.push(rosterCopy.target);
1670
1825
  if (rosterCopy.action === "overwritten") result.overwritten.push(rosterCopy.target);
1671
1826
  if (rosterCopy.action === "conflict") result.conflicts.push(`${rosterCopy.target} -> ${rosterCopy.conflictPath}`);
1672
- 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, {
1673
1828
  force: Boolean(options.force),
1674
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1829
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1675
1830
  });
1676
1831
  if (modelRoutingCopy.action === "created") result.copied.push(modelRoutingCopy.target);
1677
1832
  if (modelRoutingCopy.action === "unchanged") result.unchanged.push(modelRoutingCopy.target);
1678
1833
  if (modelRoutingCopy.action === "overwritten") result.overwritten.push(modelRoutingCopy.target);
1679
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}`);
1680
1851
  const manifest = {
1852
+ schemaVersion: 2,
1681
1853
  packageName: PACKAGE_NAME,
1682
1854
  packageVersion: PACKAGE_VERSION,
1683
1855
  stack,
@@ -1686,19 +1858,20 @@ function initProject(options) {
1686
1858
  libraryFolders: [...LIBRARY_FOLDERS],
1687
1859
  agentRoster: DEFAULT_AGENT_ROSTER_TARGET,
1688
1860
  modelRouting: DEFAULT_MODEL_ROUTING_TARGET,
1689
- templateHashes
1861
+ templateHashes,
1862
+ assetHashes: hashManagedAssets(managedAssets)
1690
1863
  };
1691
- 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)}
1692
1865
  `);
1693
- 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)}
1694
1867
  `);
1695
- const overridesPath = join10(cwd, ".agent-kit", "overrides.json");
1868
+ const overridesPath = join11(cwd, ".agent-kit", "overrides.json");
1696
1869
  if (!existsSync10(overridesPath)) writeText(overridesPath, `${JSON.stringify({ templates: {} }, null, 2)}
1697
1870
  `);
1698
1871
  for (const template of CI_TEMPLATE_FILES) {
1699
- const ciCopy = copyTextWithConflict(join10(packageRoot, template.source), cwd, template.target, {
1872
+ const ciCopy = copyTextWithConflict(join11(packageRoot, template.source), cwd, template.target, {
1700
1873
  force: Boolean(options.force),
1701
- conflictRoot: join10(cwd, ".agent-kit", "conflicts")
1874
+ conflictRoot: join11(cwd, ".agent-kit", "conflicts")
1702
1875
  });
1703
1876
  if (ciCopy.action === "created") result.copied.push(ciCopy.target);
1704
1877
  if (ciCopy.action === "unchanged") result.unchanged.push(ciCopy.target);
@@ -1722,10 +1895,278 @@ function initProject(options) {
1722
1895
  return result;
1723
1896
  }
1724
1897
  function readManifest(cwd) {
1725
- const manifestPath = join10(cwd, ".agent-kit", "manifest.json");
1898
+ const manifestPath = join11(cwd, ".agent-kit", "manifest.json");
1726
1899
  if (!existsSync10(manifestPath)) return null;
1727
- 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;
1728
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
+ };
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 });
1729
2170
 
1730
2171
  // src/install/audit.ts
1731
2172
  var REQUIRED_AGENT_IDS = [
@@ -1770,13 +2211,17 @@ var REQUIRED_SCHEMA_FILES = [
1770
2211
  "agent-roster.schema.json",
1771
2212
  "council-session.schema.json",
1772
2213
  "audit-report.schema.json",
2214
+ "audit-report-v2.schema.json",
1773
2215
  "model-routing.schema.json",
1774
2216
  "project-context.schema.json",
1775
2217
  "correction-rules.schema.json",
1776
2218
  "session-event.schema.json",
1777
2219
  "studio-session.schema.json",
1778
2220
  "onboarding-state.schema.json",
1779
- "agentic-level.schema.json"
2221
+ "agentic-level.schema.json",
2222
+ "orchestrator.schema.json",
2223
+ "runtime-run.schema.json",
2224
+ "runtime-event.schema.json"
1780
2225
  ];
1781
2226
  var COUNCIL_SESSION_DIR = ".agent-kit/council-sessions";
1782
2227
  var READINESS_ORDER = ["needs-setup", "baseline-setup", "needs-improvement", "best-practice-candidate"];
@@ -1795,24 +2240,24 @@ function includesAll(text, values) {
1795
2240
  return values.every((value) => lower.includes(value.toLowerCase()));
1796
2241
  }
1797
2242
  function readDoc(cwd, file) {
1798
- const path = join11(cwd, file);
1799
- return existsSync11(path) ? readFileSync9(path, "utf8") : "";
2243
+ const path = join13(cwd, file);
2244
+ return existsSync12(path) ? readFileSync11(path, "utf8") : "";
1800
2245
  }
1801
2246
  function isPackageRepository(cwd) {
1802
- const packagePath = join11(cwd, "package.json");
1803
- if (!existsSync11(packagePath)) return false;
2247
+ const packagePath = join13(cwd, "package.json");
2248
+ if (!existsSync12(packagePath)) return false;
1804
2249
  try {
1805
- const packageJson = JSON.parse(readFileSync9(packagePath, "utf8"));
1806
- 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"));
1807
2252
  } catch {
1808
2253
  return false;
1809
2254
  }
1810
2255
  }
1811
2256
  function readOverrides(cwd) {
1812
- const path = join11(cwd, ".agent-kit", "overrides.json");
1813
- if (!existsSync11(path)) return {};
2257
+ const path = join13(cwd, ".agent-kit", "overrides.json");
2258
+ if (!existsSync12(path)) return {};
1814
2259
  try {
1815
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
2260
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
1816
2261
  const templates = parsed.templates ?? {};
1817
2262
  return Object.fromEntries(
1818
2263
  Object.entries(templates).map(([file, override]) => [
@@ -1825,8 +2270,8 @@ function readOverrides(cwd) {
1825
2270
  }
1826
2271
  }
1827
2272
  function readTemplate(stack, file) {
1828
- const path = join11(findPackageRoot(), "templates", stack, file);
1829
- return existsSync11(path) ? readFileSync9(path, "utf8") : null;
2273
+ const path = join13(findPackageRoot(), "templates", stack, file);
2274
+ return existsSync12(path) ? readFileSync11(path, "utf8") : null;
1830
2275
  }
1831
2276
  function asStringArray(value) {
1832
2277
  if (!Array.isArray(value)) return [];
@@ -1836,8 +2281,8 @@ function isRecord(value) {
1836
2281
  return typeof value === "object" && value !== null;
1837
2282
  }
1838
2283
  function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGENT_ROSTER_TARGET) {
1839
- const rosterPath = join11(cwd, rosterRelativePath);
1840
- if (!existsSync11(rosterPath)) {
2284
+ const rosterPath = join13(cwd, rosterRelativePath);
2285
+ if (!existsSync12(rosterPath)) {
1841
2286
  findings.push({
1842
2287
  level: "fail",
1843
2288
  area: "agents",
@@ -1848,7 +2293,7 @@ function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGEN
1848
2293
  }
1849
2294
  let roster;
1850
2295
  try {
1851
- const parsed = JSON.parse(readFileSync9(rosterPath, "utf8"));
2296
+ const parsed = JSON.parse(readFileSync11(rosterPath, "utf8"));
1852
2297
  if (!isRecord(parsed)) throw new Error("Roster must be a JSON object.");
1853
2298
  const contractResult = AgentRosterContract.safeParse(parsed);
1854
2299
  if (!contractResult.success) {
@@ -2035,15 +2480,15 @@ function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGEN
2035
2480
  }
2036
2481
  }
2037
2482
  function addCouncilSessionRecordFindings(cwd, findings) {
2038
- const sessionsRoot = join11(cwd, COUNCIL_SESSION_DIR);
2039
- if (!existsSync11(sessionsRoot)) return;
2483
+ const sessionsRoot = join13(cwd, COUNCIL_SESSION_DIR);
2484
+ if (!existsSync12(sessionsRoot)) return;
2040
2485
  const sessionFiles = listFilesRecursive(sessionsRoot).filter((file) => file.endsWith(".json") && !/[\\/]/.test(file));
2041
2486
  if (sessionFiles.length === 0) return;
2042
2487
  let invalidCount = 0;
2043
2488
  for (const sessionFile of sessionFiles) {
2044
2489
  const displayPath = `${COUNCIL_SESSION_DIR}/${sessionFile}`;
2045
2490
  try {
2046
- const parsed = JSON.parse(readFileSync9(join11(sessionsRoot, sessionFile), "utf8"));
2491
+ const parsed = JSON.parse(readFileSync11(join13(sessionsRoot, sessionFile), "utf8"));
2047
2492
  const contractResult = CouncilSessionContract.safeParse(parsed);
2048
2493
  if (!contractResult.success) {
2049
2494
  invalidCount += 1;
@@ -2074,8 +2519,8 @@ function addCouncilSessionRecordFindings(cwd, findings) {
2074
2519
  }
2075
2520
  function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/schemas") {
2076
2521
  for (const schemaFile of REQUIRED_SCHEMA_FILES) {
2077
- const schemaPath = join11(cwd, schemaRootRelativePath, schemaFile);
2078
- if (!existsSync11(schemaPath)) {
2522
+ const schemaPath = join13(cwd, schemaRootRelativePath, schemaFile);
2523
+ if (!existsSync12(schemaPath)) {
2079
2524
  findings.push({
2080
2525
  level: "warn",
2081
2526
  area: "agents",
@@ -2085,7 +2530,7 @@ function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/s
2085
2530
  continue;
2086
2531
  }
2087
2532
  try {
2088
- const parsed = JSON.parse(readFileSync9(schemaPath, "utf8"));
2533
+ const parsed = JSON.parse(readFileSync11(schemaPath, "utf8"));
2089
2534
  if (!isRecord(parsed) || typeof parsed.$schema !== "string" || !isRecord(parsed.properties)) {
2090
2535
  throw new Error("Schema file is missing JSON Schema metadata.");
2091
2536
  }
@@ -2104,9 +2549,54 @@ function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/s
2104
2549
  }
2105
2550
  }
2106
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
+ }
2107
2597
  function addAgentStudioFindings(cwd, findings) {
2108
- const contextPath = join11(cwd, CONTEXT_JSON);
2109
- if (!existsSync11(contextPath)) {
2598
+ const contextPath = join13(cwd, CONTEXT_JSON);
2599
+ if (!existsSync12(contextPath)) {
2110
2600
  findings.push({
2111
2601
  level: "warn",
2112
2602
  area: "studio",
@@ -2115,7 +2605,7 @@ function addAgentStudioFindings(cwd, findings) {
2115
2605
  });
2116
2606
  } else {
2117
2607
  try {
2118
- const parsed = JSON.parse(readFileSync9(contextPath, "utf8"));
2608
+ const parsed = JSON.parse(readFileSync11(contextPath, "utf8"));
2119
2609
  const result = ProjectContextContract.safeParse(parsed);
2120
2610
  if (!result.success) {
2121
2611
  findings.push({
@@ -2179,8 +2669,8 @@ function addAgentStudioFindings(cwd, findings) {
2179
2669
  });
2180
2670
  }
2181
2671
  }
2182
- const contextMdPath = join11(cwd, CONTEXT_MD);
2183
- if (existsSync11(contextPath) && !existsSync11(contextMdPath)) {
2672
+ const contextMdPath = join13(cwd, CONTEXT_MD);
2673
+ if (existsSync12(contextPath) && !existsSync12(contextMdPath)) {
2184
2674
  findings.push({
2185
2675
  level: "warn",
2186
2676
  area: "studio",
@@ -2189,10 +2679,10 @@ function addAgentStudioFindings(cwd, findings) {
2189
2679
  });
2190
2680
  }
2191
2681
  for (const relativePath of [PROJECT_RULES_JSON, AGENT_RULES_JSON]) {
2192
- const path = join11(cwd, relativePath);
2193
- if (!existsSync11(path)) continue;
2682
+ const path = join13(cwd, relativePath);
2683
+ if (!existsSync12(path)) continue;
2194
2684
  try {
2195
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
2685
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
2196
2686
  const result = CorrectionRulesContract.safeParse(parsed);
2197
2687
  if (!result.success) {
2198
2688
  findings.push({
@@ -2217,9 +2707,9 @@ function addAgentStudioFindings(cwd, findings) {
2217
2707
  });
2218
2708
  }
2219
2709
  }
2220
- const studioExportPath = join11(cwd, STUDIO_EXPORT_HTML);
2221
- if (existsSync11(studioExportPath)) {
2222
- const exportHtml = readFileSync9(studioExportPath, "utf8");
2710
+ const studioExportPath = join13(cwd, STUDIO_EXPORT_HTML);
2711
+ if (existsSync12(studioExportPath)) {
2712
+ const exportHtml = readFileSync11(studioExportPath, "utf8");
2223
2713
  if (containsLikelySecret(exportHtml)) {
2224
2714
  findings.push({
2225
2715
  level: "fail",
@@ -2242,8 +2732,8 @@ function addAgentStudioFindings(cwd, findings) {
2242
2732
  });
2243
2733
  }
2244
2734
  }
2245
- const sessionsRoot = join11(cwd, COUNCIL_SESSION_DIR);
2246
- if (!existsSync11(sessionsRoot)) return;
2735
+ const sessionsRoot = join13(cwd, COUNCIL_SESSION_DIR);
2736
+ if (!existsSync12(sessionsRoot)) return;
2247
2737
  const files = listFilesRecursive(sessionsRoot);
2248
2738
  const studioSessionFiles = files.filter((file) => /[\\/]session\.json$/.test(file));
2249
2739
  for (const sessionFile of studioSessionFiles) {
@@ -2252,10 +2742,10 @@ function addAgentStudioFindings(cwd, findings) {
2252
2742
  const sessionDir2 = sessionFile.replace(/[\\/]session\.json$/, "");
2253
2743
  const normalizedSessionDir = sessionDir2.replace(/\\/g, "/");
2254
2744
  const eventsRelative = `${COUNCIL_SESSION_DIR}/${normalizedSessionDir}/events.jsonl`;
2255
- const sessionDirPath = join11(sessionsRoot, sessionDir2);
2745
+ const sessionDirPath = join13(sessionsRoot, sessionDir2);
2256
2746
  let sessionResult = null;
2257
2747
  try {
2258
- sessionResult = StudioSessionContract.safeParse(JSON.parse(readFileSync9(join11(sessionDirPath, "session.json"), "utf8")));
2748
+ sessionResult = StudioSessionContract.safeParse(JSON.parse(readFileSync11(join13(sessionDirPath, "session.json"), "utf8")));
2259
2749
  if (!sessionResult.success) {
2260
2750
  findings.push({
2261
2751
  level: "fail",
@@ -2274,8 +2764,8 @@ function addAgentStudioFindings(cwd, findings) {
2274
2764
  });
2275
2765
  continue;
2276
2766
  }
2277
- const eventsPath2 = join11(sessionDirPath, "events.jsonl");
2278
- if (!existsSync11(eventsPath2)) {
2767
+ const eventsPath2 = join13(sessionDirPath, "events.jsonl");
2768
+ if (!existsSync12(eventsPath2)) {
2279
2769
  findings.push({
2280
2770
  level: "fail",
2281
2771
  area: "studio",
@@ -2284,7 +2774,7 @@ function addAgentStudioFindings(cwd, findings) {
2284
2774
  });
2285
2775
  continue;
2286
2776
  }
2287
- const eventText = readFileSync9(eventsPath2, "utf8");
2777
+ const eventText = readFileSync11(eventsPath2, "utf8");
2288
2778
  if (containsLikelySecret(eventText)) {
2289
2779
  findings.push({
2290
2780
  level: "fail",
@@ -2319,7 +2809,7 @@ function addAgentStudioFindings(cwd, findings) {
2319
2809
  });
2320
2810
  }
2321
2811
  }
2322
- if (!existsSync11(join11(sessionDirPath, "index.md")) || !existsSync11(join11(sessionDirPath, "transcript.md"))) {
2812
+ if (!existsSync12(join13(sessionDirPath, "index.md")) || !existsSync12(join13(sessionDirPath, "transcript.md"))) {
2323
2813
  findings.push({
2324
2814
  level: "warn",
2325
2815
  area: "studio",
@@ -2327,8 +2817,8 @@ function addAgentStudioFindings(cwd, findings) {
2327
2817
  remediation: "Run agent-kit session render so humans can inspect the current agent transcript and handoffs."
2328
2818
  });
2329
2819
  } else {
2330
- const indexText = readFileSync9(join11(sessionDirPath, "index.md"), "utf8");
2331
- 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");
2332
2822
  if (containsLikelySecret(indexText) || containsLikelySecret(transcriptText)) {
2333
2823
  findings.push({
2334
2824
  level: "fail",
@@ -2337,7 +2827,7 @@ function addAgentStudioFindings(cwd, findings) {
2337
2827
  remediation: "Regenerate Markdown after redacting sensitive values from the event log."
2338
2828
  });
2339
2829
  }
2340
- if (statSync(eventsPath2).mtimeMs > statSync(join11(sessionDirPath, "index.md")).mtimeMs) {
2830
+ if (statSync3(eventsPath2).mtimeMs > statSync3(join13(sessionDirPath, "index.md")).mtimeMs) {
2341
2831
  findings.push({
2342
2832
  level: "warn",
2343
2833
  area: "studio",
@@ -2387,8 +2877,8 @@ function addCouncilDocFindings(cwd, findings) {
2387
2877
  }
2388
2878
  function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".agent-kit/assistant-adapters", docsCwd = cwd) {
2389
2879
  const adaptersDoc = readDoc(docsCwd, "ASSISTANT_ADAPTERS.md");
2390
- const adapterRoot = join11(cwd, adapterRootRelativePath);
2391
- if (!existsSync11(adapterRoot)) {
2880
+ const adapterRoot = join13(cwd, adapterRootRelativePath);
2881
+ if (!existsSync12(adapterRoot)) {
2392
2882
  findings.push({
2393
2883
  level: "warn",
2394
2884
  area: "agents",
@@ -2417,7 +2907,7 @@ function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".
2417
2907
  message: "ASSISTANT_ADAPTERS.md maps the council roster to tool-specific instruction surfaces."
2418
2908
  });
2419
2909
  }
2420
- if (assistantAdapterRowIsActive(adaptersDoc, "Cursor") && !existsSync11(join11(cwd, ".cursor/agents/planner.md"))) {
2910
+ if (assistantAdapterRowIsActive(adaptersDoc, "Cursor") && !existsSync12(join13(cwd, ".cursor/agents/planner.md"))) {
2421
2911
  findings.push({
2422
2912
  level: "warn",
2423
2913
  area: "agents",
@@ -2425,7 +2915,7 @@ function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".
2425
2915
  remediation: "Run agent-kit init --activate cursor to generate council subagents from the roster."
2426
2916
  });
2427
2917
  }
2428
- 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"))) {
2429
2919
  findings.push({
2430
2920
  level: "warn",
2431
2921
  area: "agents",
@@ -2473,8 +2963,8 @@ function addModelRoutingFindings(cwd, findings, routingRelativePath = DEFAULT_MO
2473
2963
  message: "MODEL_ROUTING.md documents agent model profiles and IDE enforcement limits."
2474
2964
  });
2475
2965
  }
2476
- const routingPath = join11(cwd, routingRelativePath);
2477
- if (!existsSync11(routingPath)) {
2966
+ const routingPath = join13(cwd, routingRelativePath);
2967
+ if (!existsSync12(routingPath)) {
2478
2968
  findings.push({
2479
2969
  level: "warn",
2480
2970
  area: "models",
@@ -2485,7 +2975,7 @@ function addModelRoutingFindings(cwd, findings, routingRelativePath = DEFAULT_MO
2485
2975
  }
2486
2976
  let routing;
2487
2977
  try {
2488
- routing = JSON.parse(readFileSync9(routingPath, "utf8"));
2978
+ routing = JSON.parse(readFileSync11(routingPath, "utf8"));
2489
2979
  } catch {
2490
2980
  findings.push({
2491
2981
  level: "warn",
@@ -2543,11 +3033,11 @@ function addTemplateHashFindings(cwd, findings) {
2543
3033
  if (!manifest) return;
2544
3034
  const overrides = readOverrides(cwd);
2545
3035
  for (const doc of ROOT_DOCS) {
2546
- const targetPath = join11(cwd, doc);
2547
- if (!existsSync11(targetPath)) continue;
3036
+ const targetPath = join13(cwd, doc);
3037
+ if (!existsSync12(targetPath)) continue;
2548
3038
  const currentTemplate = readTemplate(manifest.stack, doc);
2549
3039
  if (!currentTemplate) continue;
2550
- const targetHash = sha256(readFileSync9(targetPath, "utf8"));
3040
+ const targetHash = sha256(readFileSync11(targetPath, "utf8"));
2551
3041
  const currentTemplateHash = sha256(currentTemplate);
2552
3042
  const installedTemplateHash = manifest.templateHashes?.[doc];
2553
3043
  const override = overrides[doc];
@@ -2839,27 +3329,25 @@ function addMessagingFindings(cwd, findings) {
2839
3329
  });
2840
3330
  }
2841
3331
  }
2842
- function auditProject(cwd) {
3332
+ function auditProject(cwd, options = {}) {
2843
3333
  const findings = [];
2844
3334
  const manifest = readManifest(cwd);
2845
3335
  const packageRepository = isPackageRepository(cwd);
2846
- const packageSourceMode = packageRepository && !manifest;
2847
- const docsCwd = packageSourceMode ? join11(cwd, "templates", "next-supabase") : cwd;
2848
- if (!manifest) {
2849
- if (packageRepository) {
2850
- findings.push({
2851
- level: "pass",
2852
- area: "install",
2853
- message: "Package source repository mode detected; installed-project manifest is not required."
2854
- });
2855
- } else {
2856
- findings.push({
2857
- level: "fail",
2858
- area: "install",
2859
- message: "Project has no .agent-kit/manifest.json.",
2860
- remediation: "Run agent-kit init --stack next-supabase."
2861
- });
2862
- }
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
+ });
2863
3351
  } else {
2864
3352
  findings.push({
2865
3353
  level: "pass",
@@ -2867,17 +3355,20 @@ function auditProject(cwd) {
2867
3355
  message: `Agent kit installed at version ${manifest.packageVersion}.`
2868
3356
  });
2869
3357
  }
2870
- addTemplateHashFindings(cwd, findings);
3358
+ if (!packageSourceMode) addTemplateHashFindings(cwd, findings);
2871
3359
  addAgentRosterFindings(cwd, findings, packageSourceMode ? "rosters/next-supabase-default-council.json" : DEFAULT_AGENT_ROSTER_TARGET);
2872
3360
  addSchemaFindings(cwd, findings, packageSourceMode ? "schemas" : ".agent-kit/schemas");
2873
- addCouncilSessionRecordFindings(cwd, findings);
2874
- if (!packageRepository || existsSync11(join11(cwd, CONTEXT_JSON)) || existsSync11(join11(cwd, COUNCIL_SESSION_DIR))) {
2875
- 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
+ }
2876
3367
  }
2877
3368
  for (const doc of ROOT_DOCS) {
2878
- const docPath = join11(docsCwd, doc);
3369
+ const docPath = join13(docsCwd, doc);
2879
3370
  const displayPath = packageSourceMode ? `templates/next-supabase/${doc}` : doc;
2880
- if (existsSync11(docPath)) {
3371
+ if (existsSync12(docPath)) {
2881
3372
  findings.push({ level: "pass", area: "docs", message: `${displayPath} exists.` });
2882
3373
  } else {
2883
3374
  findings.push({
@@ -2895,7 +3386,7 @@ function auditProject(cwd) {
2895
3386
  addQualityGateFindings(docsCwd, findings);
2896
3387
  addUpgradeFindings(docsCwd, findings);
2897
3388
  addProjectEvidenceFindings(docsCwd, findings);
2898
- addProjectRealityFindings(cwd, findings, { packageRepository });
3389
+ findings.push(...projectRealityRules.evaluate({ cwd, packageRepository, observedAt: (/* @__PURE__ */ new Date()).toISOString() }));
2899
3390
  const security = readDoc(docsCwd, "SECURITY.md");
2900
3391
  if (!includesAny(security, ["OWASP", "Top 10"])) {
2901
3392
  findings.push({
@@ -2941,125 +3432,7 @@ function auditProject(cwd) {
2941
3432
  }
2942
3433
  return findings;
2943
3434
  }
2944
- function readPackageJson2(cwd) {
2945
- const path = join11(cwd, "package.json");
2946
- if (!existsSync11(path)) return null;
2947
- try {
2948
- return JSON.parse(readFileSync9(path, "utf8"));
2949
- } catch {
2950
- return null;
2951
- }
2952
- }
2953
- function containsLikelySecretForAudit(relativeFile, content) {
2954
- const normalized = relativeFile.replace(/\\/g, "/");
2955
- const testSecretFixture = ["sk", "test", "fake", "secret", "value"].join("_");
2956
- if (normalized.startsWith("tests/") && content.includes(`const fakeSecret = "${testSecretFixture}"`) && content.includes("not.toContain(fakeSecret)")) {
2957
- return containsLikelySecret(content.split(testSecretFixture).join("[TEST_SECRET_FIXTURE]"));
2958
- }
2959
- return containsLikelySecret(content);
2960
- }
2961
- function addProjectRealityFindings(cwd, findings, options = {}) {
2962
- const migrationsDir = join11(cwd, "supabase", "migrations");
2963
- if (existsSync11(migrationsDir)) {
2964
- const sqlFiles = listFilesRecursive(migrationsDir).filter((file) => file.endsWith(".sql"));
2965
- if (sqlFiles.length === 0) {
2966
- findings.push({
2967
- level: "warn",
2968
- area: "project-reality",
2969
- message: "supabase/migrations exists but contains no SQL migration files.",
2970
- remediation: "Add versioned SQL migrations or remove the empty migrations directory if Supabase is not in use."
2971
- });
2972
- } else {
2973
- const rlsFiles = sqlFiles.filter((file) => {
2974
- const content = readFileSync9(join11(migrationsDir, file), "utf8");
2975
- return /enable\s+row\s+level\s+security/i.test(content);
2976
- });
2977
- if (rlsFiles.length === 0) {
2978
- findings.push({
2979
- level: "fail",
2980
- area: "project-reality",
2981
- message: "No Supabase migration enables row level security.",
2982
- remediation: "Add `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` (or equivalent) in supabase/migrations before shipping user data."
2983
- });
2984
- } else {
2985
- findings.push({
2986
- level: "pass",
2987
- area: "project-reality",
2988
- message: `Supabase migrations enable RLS in ${rlsFiles.length} file(s).`
2989
- });
2990
- }
2991
- }
2992
- }
2993
- const packageJson = readPackageJson2(cwd);
2994
- if (!packageJson) {
2995
- findings.push({
2996
- level: "warn",
2997
- area: "project-reality",
2998
- message: "No package.json found to verify test scripts.",
2999
- remediation: "Add package.json with test, lint, and build scripts appropriate to the stack."
3000
- });
3001
- } else {
3002
- const scripts = packageJson.scripts ?? {};
3003
- const testScript = scripts.test ?? scripts["test:unit"] ?? scripts["test:ci"];
3004
- if (!testScript) {
3005
- findings.push({
3006
- level: "warn",
3007
- area: "project-reality",
3008
- message: "package.json has no test script (test, test:unit, or test:ci).",
3009
- remediation: "Add a test script and document it in TESTING.md."
3010
- });
3011
- } else {
3012
- findings.push({
3013
- level: "pass",
3014
- area: "project-reality",
3015
- message: "package.json defines a test script."
3016
- });
3017
- }
3018
- }
3019
- const trackedSourceFiles = listFilesRecursive(cwd).filter((file) => {
3020
- if (file.includes("node_modules/") || file.includes(".agent-kit/")) return false;
3021
- return /\.(ts|tsx|js|jsx|env|json)$/.test(file);
3022
- });
3023
- const secretHits = trackedSourceFiles.map((file) => {
3024
- const content = readFileSync9(join11(cwd, file), "utf8");
3025
- return containsLikelySecretForAudit(file, content) ? file : null;
3026
- }).filter((file) => file !== null).slice(0, 5);
3027
- if (secretHits.length > 0) {
3028
- findings.push({
3029
- level: "fail",
3030
- area: "project-reality",
3031
- message: `Possible committed secret patterns detected in: ${secretHits.join(", ")}.`,
3032
- remediation: "Remove secrets from tracked files, rotate exposed credentials, and use environment variables."
3033
- });
3034
- } else if (trackedSourceFiles.length > 0) {
3035
- findings.push({
3036
- level: "pass",
3037
- area: "project-reality",
3038
- message: "No obvious committed secret patterns detected in tracked source files."
3039
- });
3040
- }
3041
- if (options.packageRepository) {
3042
- findings.push({
3043
- level: "pass",
3044
- area: "project-reality",
3045
- message: "Package source repository mode does not require installed-project context files."
3046
- });
3047
- } else if (!existsSync11(join11(cwd, CONTEXT_JSON))) {
3048
- findings.push({
3049
- level: "warn",
3050
- area: "project-reality",
3051
- message: ".agent-kit/project-context.json is missing.",
3052
- remediation: "Run agent-kit init or agent-kit context init to create project context."
3053
- });
3054
- } else {
3055
- findings.push({
3056
- level: "pass",
3057
- area: "project-reality",
3058
- message: ".agent-kit/project-context.json exists."
3059
- });
3060
- }
3061
- }
3062
- function createReadiness(findings, summary2) {
3435
+ function createAuditReadiness(findings, summary2) {
3063
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);
3064
3437
  if (summary2.fail > 0) {
3065
3438
  return {
@@ -3088,11 +3461,16 @@ function createReadiness(findings, summary2) {
3088
3461
  nextActions
3089
3462
  };
3090
3463
  }
3091
- function createAuditReport(cwd) {
3092
- 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
+ }));
3093
3471
  const summary2 = { pass: 0, warn: 0, fail: 0 };
3094
3472
  for (const finding of findings) summary2[finding.level] += 1;
3095
- return { summary: summary2, readiness: createReadiness(findings, summary2), findings };
3473
+ return { summary: summary2, readiness: createAuditReadiness(findings, summary2), findings };
3096
3474
  }
3097
3475
 
3098
3476
  // src/install/adapter-validate.ts
@@ -3131,7 +3509,7 @@ function report(target, findings) {
3131
3509
  }
3132
3510
  function readJson(path) {
3133
3511
  try {
3134
- return JSON.parse(readFileSync10(path, "utf8"));
3512
+ return JSON.parse(readFileSync12(path, "utf8"));
3135
3513
  } catch {
3136
3514
  return null;
3137
3515
  }
@@ -3145,24 +3523,24 @@ function isSafeRelativePath(path) {
3145
3523
  return !normalized.startsWith("/") && !normalized.startsWith("../") && !normalized.includes("/../");
3146
3524
  }
3147
3525
  function findAntigravityLayout(cwd) {
3148
- const sourcePlugin = join12(cwd, "antigravity", "plugin.json");
3149
- if (existsSync12(sourcePlugin)) {
3526
+ const sourcePlugin = join14(cwd, "antigravity", "plugin.json");
3527
+ if (existsSync13(sourcePlugin)) {
3150
3528
  return {
3151
3529
  mode: "source",
3152
- pluginRoot: join12(cwd, "antigravity"),
3153
- commandsRoot: join12(cwd, ANTIGRAVITY_COMMANDS_SOURCE_DIR),
3154
- runtimeSkillsRoot: join12(cwd, RUNTIME_SKILLS_SOURCE_DIR),
3155
- 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")
3156
3534
  };
3157
3535
  }
3158
- const installedPlugin = join12(cwd, ".antigravity", "agent-kit", "plugin.json");
3159
- if (existsSync12(installedPlugin)) {
3536
+ const installedPlugin = join14(cwd, ".antigravity", "agent-kit", "plugin.json");
3537
+ if (existsSync13(installedPlugin)) {
3160
3538
  return {
3161
3539
  mode: "installed",
3162
- pluginRoot: join12(cwd, ".antigravity", "agent-kit"),
3163
- commandsRoot: join12(cwd, ANTIGRAVITY_COMMANDS_TARGET_DIR),
3164
- runtimeSkillsRoot: join12(cwd, ANTIGRAVITY_RUNTIME_SKILLS_TARGET_DIR),
3165
- 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")
3166
3544
  };
3167
3545
  }
3168
3546
  return null;
@@ -3186,8 +3564,8 @@ function validateAntigravityCommands(layout, findings) {
3186
3564
  const commandNames = /* @__PURE__ */ new Set();
3187
3565
  for (const command of REQUIRED_COMMANDS) {
3188
3566
  const relativePath = `${command}.toml`;
3189
- const path = join12(layout.commandsRoot, relativePath);
3190
- if (!existsSync12(path)) {
3567
+ const path = join14(layout.commandsRoot, relativePath);
3568
+ if (!existsSync13(path)) {
3191
3569
  findings.push({
3192
3570
  level: "fail",
3193
3571
  area: "commands",
@@ -3196,7 +3574,7 @@ function validateAntigravityCommands(layout, findings) {
3196
3574
  });
3197
3575
  continue;
3198
3576
  }
3199
- const text = readFileSync10(path, "utf8");
3577
+ const text = readFileSync12(path, "utf8");
3200
3578
  addSecretFinding(relativePath, text, findings);
3201
3579
  const name = commandField(text, "name");
3202
3580
  const description = commandField(text, "description");
@@ -3253,7 +3631,7 @@ function validateAntigravityCommands(layout, findings) {
3253
3631
  }
3254
3632
  }
3255
3633
  function validateAntigravityPlugin(layout, findings) {
3256
- const pluginPath = join12(layout.pluginRoot, "plugin.json");
3634
+ const pluginPath = join14(layout.pluginRoot, "plugin.json");
3257
3635
  const plugin = readJson(pluginPath);
3258
3636
  if (!plugin || !isRecord2(plugin)) {
3259
3637
  findings.push({
@@ -3294,8 +3672,8 @@ function validateAntigravityPlugin(layout, findings) {
3294
3672
  });
3295
3673
  continue;
3296
3674
  }
3297
- const resolved = join12(layout.pluginRoot, path);
3298
- if (!existsSync12(resolved)) {
3675
+ const resolved = join14(layout.pluginRoot, path);
3676
+ if (!existsSync13(resolved)) {
3299
3677
  findings.push({
3300
3678
  level: "fail",
3301
3679
  area: "manifest",
@@ -3304,7 +3682,7 @@ function validateAntigravityPlugin(layout, findings) {
3304
3682
  });
3305
3683
  }
3306
3684
  }
3307
- const pluginText = readFileSync10(pluginPath, "utf8");
3685
+ const pluginText = readFileSync12(pluginPath, "utf8");
3308
3686
  addSecretFinding("plugin.json", pluginText, findings);
3309
3687
  if (Array.isArray(plugin.sourceOfTruth) && plugin.sourceOfTruth.includes("AGENTS.md") && plugin.sourceOfTruth.includes(".agent-kit/agent-roster.json")) {
3310
3688
  findings.push({
@@ -3322,13 +3700,13 @@ function validateAntigravityPlugin(layout, findings) {
3322
3700
  }
3323
3701
  }
3324
3702
  function validateRuntimeSkills(cwd, layout, findings) {
3325
- 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");
3326
3704
  const canonicalSkillNames = listFilesRecursive(canonicalSkillsRoot).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, ""));
3327
3705
  const runtimeSkillFiles = listFilesRecursive(layout.runtimeSkillsRoot).filter((file) => file.endsWith("/SKILL.md") || file === "SKILL.md");
3328
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);
3329
3707
  for (const skillName of canonicalSkillNames) {
3330
- const runtimePath = join12(layout.runtimeSkillsRoot, skillName, "SKILL.md");
3331
- if (!existsSync12(runtimePath)) {
3708
+ const runtimePath = join14(layout.runtimeSkillsRoot, skillName, "SKILL.md");
3709
+ if (!existsSync13(runtimePath)) {
3332
3710
  findings.push({
3333
3711
  level: "fail",
3334
3712
  area: "runtime-skills",
@@ -3337,7 +3715,7 @@ function validateRuntimeSkills(cwd, layout, findings) {
3337
3715
  });
3338
3716
  continue;
3339
3717
  }
3340
- const text = readFileSync10(runtimePath, "utf8");
3718
+ const text = readFileSync12(runtimePath, "utf8");
3341
3719
  addSecretFinding(`${skillName}/SKILL.md`, text, findings);
3342
3720
  if (!/^---\nname: .+\ndescription: .+\n---/m.test(text)) {
3343
3721
  findings.push({
@@ -3365,7 +3743,7 @@ function validateRuntimeSkills(cwd, layout, findings) {
3365
3743
  remediation: "Add canonical skills or remove orphan runtime wrappers."
3366
3744
  });
3367
3745
  }
3368
- 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")))) {
3369
3747
  findings.push({
3370
3748
  level: "pass",
3371
3749
  area: "runtime-skills",
@@ -3386,7 +3764,7 @@ function validateAntigravity(cwd) {
3386
3764
  }
3387
3765
  ]);
3388
3766
  }
3389
- const adapterDoc = existsSync12(layout.adapterDocPath) ? readFileSync10(layout.adapterDocPath, "utf8") : "";
3767
+ const adapterDoc = existsSync13(layout.adapterDocPath) ? readFileSync12(layout.adapterDocPath, "utf8") : "";
3390
3768
  if (!adapterDoc) {
3391
3769
  findings.push({
3392
3770
  level: "fail",
@@ -3415,7 +3793,7 @@ function validateAntigravity(cwd) {
3415
3793
  validateAntigravityCommands(layout, findings);
3416
3794
  validateRuntimeSkills(cwd, layout, findings);
3417
3795
  if (layout.mode === "source") {
3418
- const packageJson = readJson(join12(cwd, "package.json"));
3796
+ const packageJson = readJson(join14(cwd, "package.json"));
3419
3797
  const files = isRecord2(packageJson) && Array.isArray(packageJson.files) ? packageJson.files : [];
3420
3798
  for (const requiredFile of ["antigravity", "runtime-skills", "assistant-adapters"]) {
3421
3799
  if (!files.includes(requiredFile)) {
@@ -3439,7 +3817,7 @@ function validateAntigravity(cwd) {
3439
3817
  }
3440
3818
  function validateBasicAdapter(cwd, target) {
3441
3819
  const findings = [];
3442
- 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"));
3443
3821
  if (isPackageSource) {
3444
3822
  const sourcePaths = {
3445
3823
  cursor: [
@@ -3452,8 +3830,8 @@ function validateBasicAdapter(cwd, target) {
3452
3830
  copilot: ["assistant-adapters/github-copilot-instructions.md", "assistant-adapters/github-next-supabase.instructions.md"]
3453
3831
  };
3454
3832
  for (const relativePath of sourcePaths[target]) {
3455
- const path = join12(cwd, relativePath);
3456
- if (!existsSync12(path)) {
3833
+ const path = join14(cwd, relativePath);
3834
+ if (!existsSync13(path)) {
3457
3835
  findings.push({
3458
3836
  level: "fail",
3459
3837
  area: "adapter",
@@ -3462,7 +3840,7 @@ function validateBasicAdapter(cwd, target) {
3462
3840
  });
3463
3841
  continue;
3464
3842
  }
3465
- const text = readFileSync10(path, "utf8");
3843
+ const text = readFileSync12(path, "utf8");
3466
3844
  addSecretFinding(relativePath, text, findings);
3467
3845
  if (!text.includes("AGENTS.md") && !text.includes("MODEL_ROUTING.md")) {
3468
3846
  findings.push({
@@ -3486,8 +3864,8 @@ function validateBasicAdapter(cwd, target) {
3486
3864
  return report(target, findings);
3487
3865
  }
3488
3866
  function readAssistantAdaptersDoc(cwd) {
3489
- const path = join12(cwd, "ASSISTANT_ADAPTERS.md");
3490
- return existsSync12(path) ? readFileSync10(path, "utf8") : "";
3867
+ const path = join14(cwd, "ASSISTANT_ADAPTERS.md");
3868
+ return existsSync13(path) ? readFileSync12(path, "utf8") : "";
3491
3869
  }
3492
3870
  function adaptersRowIsActive(doc, toolLabel) {
3493
3871
  return assistantAdapterRowIsActive(doc, toolLabel);
@@ -3496,8 +3874,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3496
3874
  const findings = [];
3497
3875
  const adaptersDoc = readAssistantAdaptersDoc(cwd);
3498
3876
  if (target === "cursor") {
3499
- const rulesPath = join12(cwd, ".cursor/rules/cursor-agent-kit.mdc");
3500
- if (!existsSync12(rulesPath)) {
3877
+ const rulesPath = join14(cwd, ".cursor/rules/cursor-agent-kit.mdc");
3878
+ if (!existsSync13(rulesPath)) {
3501
3879
  findings.push({
3502
3880
  level: "fail",
3503
3881
  area: "adapter",
@@ -3505,10 +3883,10 @@ function validateInstalledIdeAdapter(cwd, target) {
3505
3883
  remediation: "Run agent-kit init or agent-kit init --activate cursor."
3506
3884
  });
3507
3885
  } else {
3508
- addSecretFinding(".cursor/rules/cursor-agent-kit.mdc", readFileSync10(rulesPath, "utf8"), findings);
3886
+ addSecretFinding(".cursor/rules/cursor-agent-kit.mdc", readFileSync12(rulesPath, "utf8"), findings);
3509
3887
  }
3510
- const plannerAgent = join12(cwd, ".cursor/agents/planner.md");
3511
- if (existsSync12(plannerAgent)) {
3888
+ const plannerAgent = join14(cwd, ".cursor/agents/planner.md");
3889
+ if (existsSync13(plannerAgent)) {
3512
3890
  findings.push({
3513
3891
  level: "pass",
3514
3892
  area: "adapter",
@@ -3522,8 +3900,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3522
3900
  remediation: "Run agent-kit init --activate cursor to generate council subagents from the roster."
3523
3901
  });
3524
3902
  }
3525
- const skillSample = join12(cwd, ".cursor/skills/planning-council/SKILL.md");
3526
- if (existsSync12(skillSample)) {
3903
+ const skillSample = join14(cwd, ".cursor/skills/planning-council/SKILL.md");
3904
+ if (existsSync13(skillSample)) {
3527
3905
  findings.push({
3528
3906
  level: "pass",
3529
3907
  area: "adapter",
@@ -3532,8 +3910,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3532
3910
  }
3533
3911
  }
3534
3912
  if (target === "claude") {
3535
- const plannerAgent = join12(cwd, ".claude/agents/planner.md");
3536
- if (!existsSync12(plannerAgent)) {
3913
+ const plannerAgent = join14(cwd, ".claude/agents/planner.md");
3914
+ if (!existsSync13(plannerAgent)) {
3537
3915
  findings.push({
3538
3916
  level: "fail",
3539
3917
  area: "adapter",
@@ -3543,8 +3921,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3543
3921
  }
3544
3922
  }
3545
3923
  if (target === "codex") {
3546
- const configPath = join12(cwd, ".codex/config.toml");
3547
- if (!existsSync12(configPath)) {
3924
+ const configPath = join14(cwd, ".codex/config.toml");
3925
+ if (!existsSync13(configPath)) {
3548
3926
  findings.push({
3549
3927
  level: "fail",
3550
3928
  area: "adapter",
@@ -3552,8 +3930,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3552
3930
  remediation: "Run agent-kit init --activate codex."
3553
3931
  });
3554
3932
  }
3555
- const plannerAgent = join12(cwd, ".codex/agents/planner.toml");
3556
- if (existsSync12(plannerAgent)) {
3933
+ const plannerAgent = join14(cwd, ".codex/agents/planner.toml");
3934
+ if (existsSync13(plannerAgent)) {
3557
3935
  findings.push({
3558
3936
  level: "pass",
3559
3937
  area: "adapter",
@@ -3569,8 +3947,8 @@ function validateInstalledIdeAdapter(cwd, target) {
3569
3947
  }
3570
3948
  }
3571
3949
  if (target === "copilot") {
3572
- const instructions = join12(cwd, ".github/copilot-instructions.md");
3573
- if (!existsSync12(instructions)) {
3950
+ const instructions = join14(cwd, ".github/copilot-instructions.md");
3951
+ if (!existsSync13(instructions)) {
3574
3952
  findings.push({
3575
3953
  level: "fail",
3576
3954
  area: "adapter",
@@ -3594,7 +3972,7 @@ function validateAdapter(cwd, target = "antigravity") {
3594
3972
  }
3595
3973
  function validatePackage(cwd) {
3596
3974
  const findings = [];
3597
- 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"));
3598
3976
  if (!sourceMode) {
3599
3977
  return report("package", [
3600
3978
  {
@@ -3606,9 +3984,40 @@ function validatePackage(cwd) {
3606
3984
  ]);
3607
3985
  }
3608
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
+ }
3609
4018
  for (const doc of ["README.md", "DOCS.md", "SPEC.md", "DECISIONS.md", "QUALITY_GATES.md", "TESTING.md", "UPGRADE.md"]) {
3610
- const path = join12(cwd, doc);
3611
- const text = existsSync12(path) ? readFileSync10(path, "utf8") : "";
4019
+ const path = join14(cwd, doc);
4020
+ const text = existsSync13(path) ? readFileSync12(path, "utf8") : "";
3612
4021
  const lower = text.toLowerCase();
3613
4022
  if (!lower.includes("antigravity") && !lower.includes("runtime command") && !lower.includes("runtime adapter")) {
3614
4023
  findings.push({
@@ -3625,7 +4034,7 @@ function validatePackage(cwd) {
3625
4034
  "examples/next-supabase-installed/.agent-kit/manifest.json",
3626
4035
  "examples/next-supabase-installed/audit-output.json"
3627
4036
  ]) {
3628
- if (!existsSync12(join12(cwd, examplePath))) {
4037
+ if (!existsSync13(join14(cwd, examplePath))) {
3629
4038
  findings.push({
3630
4039
  level: "fail",
3631
4040
  area: "examples",
@@ -3634,7 +4043,7 @@ function validatePackage(cwd) {
3634
4043
  });
3635
4044
  }
3636
4045
  }
3637
- const auditReport = createAuditReport(cwd);
4046
+ const auditReport = createAuditReport(cwd, { packageSource: true });
3638
4047
  if (auditReport.summary.fail > 0) {
3639
4048
  findings.push({
3640
4049
  level: "fail",
@@ -3659,118 +4068,227 @@ function validatePackage(cwd) {
3659
4068
  return report("package", findings);
3660
4069
  }
3661
4070
 
3662
- // src/install/diff.ts
3663
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
3664
- import { join as join13 } from "path";
3665
- function statusForTextFile(target, template) {
3666
- if (!existsSync13(target)) return "missing";
3667
- const targetHash = sha256(readFileSync11(target, "utf8"));
3668
- const templateHash = sha256(readFileSync11(template, "utf8"));
3669
- 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
+ }
3670
4089
  }
3671
- function diffProject(cwd, stack = "next-supabase") {
3672
- const packageRoot = findPackageRoot();
3673
- const templateRoot = join13(packageRoot, "templates", stack);
3674
- const libraryFolders = {
3675
- missing: [],
3676
- present: [],
3677
- 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
3678
4122
  };
3679
- const result = {
3680
- missing: [],
3681
- unchanged: [],
3682
- changed: [],
3683
- agentRoster: "missing",
3684
- modelRouting: "missing",
3685
- libraryFolders,
3686
- preview: {
3687
- wouldCreate: [],
3688
- wouldWriteConflicts: [],
3689
- wouldRefreshLibraryFolders: [...LIBRARY_FOLDERS],
3690
- wouldCreateAgentRoster: false,
3691
- wouldWriteAgentRosterConflict: false,
3692
- wouldCreateModelRouting: false,
3693
- wouldWriteModelRoutingConflict: false
3694
- }
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
+ ]
3695
4167
  };
3696
- for (const doc of ROOT_DOCS) {
3697
- const target = join13(cwd, doc);
3698
- const template = join13(templateRoot, doc);
3699
- const status = statusForTextFile(target, template);
3700
- if (status === "missing") {
3701
- result.missing.push(doc);
3702
- result.preview.wouldCreate.push(doc);
3703
- continue;
3704
- }
3705
- if (status === "unchanged") result.unchanged.push(doc);
3706
- else {
3707
- result.changed.push(doc);
3708
- result.preview.wouldWriteConflicts.push(doc);
3709
- }
3710
- }
3711
- result.agentRoster = statusForTextFile(join13(cwd, DEFAULT_AGENT_ROSTER_TARGET), join13(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE));
3712
- if (result.agentRoster === "missing") {
3713
- result.preview.wouldCreate.push(DEFAULT_AGENT_ROSTER_TARGET);
3714
- result.preview.wouldCreateAgentRoster = true;
3715
- }
3716
- if (result.agentRoster === "changed") {
3717
- result.preview.wouldWriteConflicts.push(DEFAULT_AGENT_ROSTER_TARGET);
3718
- result.preview.wouldWriteAgentRosterConflict = true;
3719
- }
3720
- result.modelRouting = statusForTextFile(join13(cwd, DEFAULT_MODEL_ROUTING_TARGET), join13(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE));
3721
- if (result.modelRouting === "missing") {
3722
- result.preview.wouldCreate.push(DEFAULT_MODEL_ROUTING_TARGET);
3723
- result.preview.wouldCreateModelRouting = true;
3724
- }
3725
- if (result.modelRouting === "changed") {
3726
- result.preview.wouldWriteConflicts.push(DEFAULT_MODEL_ROUTING_TARGET);
3727
- result.preview.wouldWriteModelRoutingConflict = true;
3728
- }
3729
- for (const folder of LIBRARY_FOLDERS) {
3730
- const target = join13(cwd, ".agent-kit", folder);
3731
- if (existsSync13(target)) libraryFolders.present.push(folder);
3732
- else libraryFolders.missing.push(folder);
3733
- }
3734
- return result;
3735
4168
  }
3736
4169
 
3737
- // src/install/update.ts
3738
- import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
3739
- import { join as join14, relative as relative2 } from "path";
3740
- function planFileUpdate(cwd, input) {
3741
- 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");
3742
4178
  const sourceHash = sha256(sourceContent);
3743
- const targetPath = resolveInside(cwd, input.target);
3744
- if (!existsSync14(targetPath)) {
3745
- 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 };
3746
4181
  }
3747
- const localHash = sha256(readFileSync12(targetPath, "utf8"));
4182
+ const localContent = readFileSync14(input.targetPath, "utf8");
4183
+ const localHash = sha256(localContent);
3748
4184
  if (localHash === sourceHash) {
3749
- 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
+ };
3750
4193
  }
3751
4194
  if (input.installedHash && localHash === input.installedHash) {
3752
- 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
+ };
3753
4203
  }
3754
4204
  if (input.force) {
3755
- 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
+ };
3756
4213
  }
3757
4214
  if (input.installedHash && input.installedHash === sourceHash) {
3758
- 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
+ };
3759
4223
  }
3760
4224
  return {
3761
4225
  target: input.target,
3762
4226
  action: "conflict",
3763
- reason: "File is locally customized and the bundled template changed; review the conflict copy.",
3764
- sourceContent
4227
+ reason: "File is locally customized and the package asset changed; review the proposed content.",
4228
+ sourceContent,
4229
+ sourceHash,
4230
+ localContent
3765
4231
  };
3766
4232
  }
3767
- function writeConflictCopy(cwd, target, content) {
3768
- const conflictRoot = join14(cwd, ".agent-kit", "conflicts");
3769
- const safeName = `${Date.now()}-${target.replace(/[^a-zA-Z0-9_.-]/g, "_")}`;
3770
- const conflictPath = join14(conflictRoot, safeName);
3771
- writeText(conflictPath, content);
3772
- 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
+ };
3773
4287
  }
4288
+
4289
+ // src/install/update.ts
4290
+ import { existsSync as existsSync17 } from "fs";
4291
+ import { join as join17 } from "path";
3774
4292
  function updateProject(options) {
3775
4293
  const cwd = options.cwd;
3776
4294
  const force = Boolean(options.force);
@@ -3783,14 +4301,14 @@ function updateProject(options) {
3783
4301
  const initResult = initProject({ cwd, force });
3784
4302
  const files2 = [
3785
4303
  ...initResult.copied.map((target) => ({ target, action: "created", reason: "Installed by init fallback." })),
3786
- ...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." })),
3787
4305
  ...initResult.overwritten.map((target) => ({ target, action: "overwritten", reason: "Overwritten by init --force fallback." })),
3788
4306
  ...initResult.conflicts.map((entry) => {
3789
4307
  const [target, conflictPath] = entry.split(" -> ");
3790
4308
  return {
3791
4309
  target: target ?? entry,
3792
4310
  action: "conflict",
3793
- reason: "Local file differed from the template during init fallback.",
4311
+ reason: "Local file differed from the package asset during init fallback.",
3794
4312
  ...conflictPath ? { conflictPath } : {}
3795
4313
  };
3796
4314
  })
@@ -3805,66 +4323,53 @@ function updateProject(options) {
3805
4323
  }
3806
4324
  const packageRoot = findPackageRoot();
3807
4325
  const stack = manifest.stack ?? "next-supabase";
3808
- const templateRoot = join14(packageRoot, "templates", stack);
3809
- if (!existsSync14(templateRoot)) {
3810
- throw new Error(`Unsupported stack profile in manifest: ${stack}`);
3811
- }
3812
- const files = [];
3813
- const templateHashes = {};
3814
- const plans = [];
3815
- for (const doc of ROOT_DOCS) {
3816
- const sourcePath = join14(templateRoot, doc);
3817
- templateHashes[doc] = sha256(readFileSync12(sourcePath, "utf8"));
3818
- plans.push(
3819
- planFileUpdate(cwd, {
3820
- target: doc,
3821
- sourcePath,
3822
- installedHash: manifest.templateHashes?.[doc],
3823
- force
3824
- })
3825
- );
3826
- }
3827
- for (const adapter2 of CURSOR_ADAPTER_FILES) {
3828
- plans.push(
3829
- planFileUpdate(cwd, {
3830
- target: adapter2.target,
3831
- sourcePath: join14(packageRoot, adapter2.source),
3832
- installedHash: void 0,
3833
- force
3834
- })
3835
- );
3836
- }
3837
- plans.push(
3838
- planFileUpdate(cwd, {
3839
- target: DEFAULT_AGENT_ROSTER_TARGET,
3840
- sourcePath: join14(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE),
3841
- installedHash: void 0,
3842
- force
3843
- }),
3844
- planFileUpdate(cwd, {
3845
- target: DEFAULT_MODEL_ROUTING_TARGET,
3846
- sourcePath: join14(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE),
3847
- 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],
3848
4335
  force
3849
4336
  })
3850
4337
  );
4338
+ const files = [];
3851
4339
  for (const plan of plans) {
3852
- const { sourceContent, ...fileResult } = plan;
4340
+ const result = { target: plan.target, action: plan.action, reason: plan.reason };
3853
4341
  if (!dryRun) {
3854
4342
  if (plan.action === "created" || plan.action === "updated" || plan.action === "overwritten") {
3855
- writeText(resolveInside(cwd, plan.target), sourceContent);
4343
+ writeText(resolveInside(cwd, plan.target), plan.sourceContent);
3856
4344
  } else if (plan.action === "conflict") {
3857
- 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;
3858
4351
  }
3859
4352
  }
3860
- 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
+ });
3861
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();
3862
4369
  if (!dryRun) {
3863
- ensureDir(join14(cwd, ".agent-kit"));
3864
- for (const folder of LIBRARY_FOLDERS) {
3865
- copyDirectory(join14(packageRoot, folder), join14(cwd, ".agent-kit", folder));
3866
- }
4370
+ const assetHashes = hashManagedAssets(assets);
3867
4371
  const updatedManifest = {
4372
+ schemaVersion: 2,
3868
4373
  packageName: PACKAGE_NAME,
3869
4374
  packageVersion: PACKAGE_VERSION,
3870
4375
  stack,
@@ -3872,17 +4377,18 @@ function updateProject(options) {
3872
4377
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3873
4378
  docs: [...ROOT_DOCS],
3874
4379
  libraryFolders: [...LIBRARY_FOLDERS],
3875
- agentRoster: DEFAULT_AGENT_ROSTER_TARGET,
3876
- modelRouting: DEFAULT_MODEL_ROUTING_TARGET,
3877
- 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
3878
4384
  };
3879
- 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)}
3880
4386
  `);
3881
4387
  }
3882
4388
  return {
3883
4389
  dryRun,
3884
4390
  files,
3885
- libraryFoldersRefreshed: [...LIBRARY_FOLDERS],
4391
+ libraryFoldersRefreshed: refreshedFolders,
3886
4392
  manifestPath: ".agent-kit/manifest.json",
3887
4393
  summary: summarize(files)
3888
4394
  };
@@ -3902,8 +4408,8 @@ function summarize(files) {
3902
4408
 
3903
4409
  // src/research/discover.ts
3904
4410
  import { Octokit } from "@octokit/rest";
3905
- import { readFileSync as readFileSync13 } from "fs";
3906
- import { join as join15 } from "path";
4411
+ import { readFileSync as readFileSync15 } from "fs";
4412
+ import { join as join18 } from "path";
3907
4413
 
3908
4414
  // src/research/config.ts
3909
4415
  import { z as z2 } from "zod";
@@ -3928,8 +4434,8 @@ var researchConfigSchema = z2.object({
3928
4434
  // src/research/discover.ts
3929
4435
  async function discoverRepos(options) {
3930
4436
  const packageRoot = findPackageRoot();
3931
- const configPath = join15(packageRoot, "research", "scan-config.json");
3932
- 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")));
3933
4439
  const token = options.token ?? process.env.GITHUB_TOKEN;
3934
4440
  if (!token) {
3935
4441
  throw new Error("GITHUB_TOKEN is required for GitHub API research discovery.");
@@ -3990,20 +4496,20 @@ async function discoverRepos(options) {
3990
4496
  }
3991
4497
  }
3992
4498
  const candidates = [...deduped.values()].slice(0, maxRepos);
3993
- const output = options.output ?? join15(options.cwd, "research", "repo-candidates.json");
4499
+ const output = options.output ?? join18(options.cwd, "research", "repo-candidates.json");
3994
4500
  writeText(output, `${JSON.stringify(candidates, null, 2)}
3995
4501
  `);
3996
4502
  return candidates;
3997
4503
  }
3998
4504
 
3999
4505
  // src/research/scan.ts
4000
- import { existsSync as existsSync16, mkdirSync as mkdirSync2, readFileSync as readFileSync15, rmSync } from "fs";
4001
- 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";
4002
4508
  import { simpleGit } from "simple-git";
4003
4509
 
4004
4510
  // src/research/analyze.ts
4005
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
4006
- import { join as join16 } from "path";
4511
+ import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
4512
+ import { join as join19 } from "path";
4007
4513
  function normalizeRelativePath(file) {
4008
4514
  return file.replace(/\\/g, "/");
4009
4515
  }
@@ -4011,8 +4517,8 @@ function hasFile(files, matcher) {
4011
4517
  return files.some((file) => matcher.test(normalizeRelativePath(file)));
4012
4518
  }
4013
4519
  function fileText(root, file) {
4014
- const path = join16(root, file);
4015
- return existsSync15(path) ? readFileSync14(path, "utf8") : "";
4520
+ const path = join19(root, file);
4521
+ return existsSync18(path) ? readFileSync16(path, "utf8") : "";
4016
4522
  }
4017
4523
  function textIncludes(root, files, matcher, terms) {
4018
4524
  const lowerTerms = terms.map((term) => term.toLowerCase());
@@ -4143,34 +4649,34 @@ ${finding.impactOnKit.map((item) => `- ${item}`).join("\n")}
4143
4649
  `;
4144
4650
  }
4145
4651
  async function scanRepos(options) {
4146
- const candidatesPath = options.candidatesPath ?? join17(options.cwd, "research", "repo-candidates.json");
4147
- if (!existsSync16(candidatesPath)) {
4652
+ const candidatesPath = options.candidatesPath ?? join20(options.cwd, "research", "repo-candidates.json");
4653
+ if (!existsSync19(candidatesPath)) {
4148
4654
  throw new Error(`Candidates file not found: ${candidatesPath}`);
4149
4655
  }
4150
- const candidates = JSON.parse(readFileSync15(candidatesPath, "utf8"));
4151
- 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");
4152
4658
  mkdirSync2(workdir, { recursive: true });
4153
- mkdirSync2(join17(options.cwd, "research", "findings"), { recursive: true });
4659
+ mkdirSync2(join20(options.cwd, "research", "findings"), { recursive: true });
4154
4660
  const findings = [];
4155
4661
  const git = simpleGit();
4156
4662
  for (const candidate of candidates) {
4157
4663
  const repoSlug = candidate.fullName.replace("/", "__");
4158
- const repoPath = join17(workdir, repoSlug);
4159
- 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 });
4160
4666
  await git.raw(["clone", "--depth", "1", candidate.htmlUrl, repoPath]);
4161
4667
  const finding = analyzeRepository(candidate, repoPath);
4162
4668
  findings.push(finding);
4163
- writeText(join17(options.cwd, "research", "findings", `${repoSlug}.md`), findingToMarkdown(finding));
4669
+ writeText(join20(options.cwd, "research", "findings", `${repoSlug}.md`), findingToMarkdown(finding));
4164
4670
  if (!options.keepClones) {
4165
- rmSync(repoPath, { recursive: true, force: true });
4671
+ rmSync2(repoPath, { recursive: true, force: true });
4166
4672
  }
4167
4673
  }
4168
4674
  return findings;
4169
4675
  }
4170
4676
 
4171
4677
  // src/research/summarize.ts
4172
- import { existsSync as existsSync17, readFileSync as readFileSync16, readdirSync as readdirSync3 } from "fs";
4173
- 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";
4174
4680
  var SUMMARY_TARGETS = {
4175
4681
  "nextjs-patterns": {
4176
4682
  title: "Next.js Patterns",
@@ -4273,12 +4779,12 @@ function renderRepoList(findings, scoreKeys) {
4273
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");
4274
4780
  }
4275
4781
  function summarizeFindings(cwd) {
4276
- const findingsDir = join18(cwd, "research", "findings");
4277
- if (!existsSync17(findingsDir)) {
4782
+ const findingsDir = join21(cwd, "research", "findings");
4783
+ if (!existsSync20(findingsDir)) {
4278
4784
  throw new Error("No research/findings directory exists. Run agent-kit research scan first.");
4279
4785
  }
4280
4786
  const findingFiles = readdirSync3(findingsDir).filter((file) => file.endsWith(".md"));
4281
- 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);
4282
4788
  const categoryCounts = countBy(findings.map((finding) => finding.category));
4283
4789
  const outputs = [];
4284
4790
  const overview = `# Research Scan Overview
@@ -4297,13 +4803,13 @@ ${countBy(findings.flatMap((finding) => finding.strongPractices)).slice(0, 12).m
4297
4803
  ## Most Repeated Gaps
4298
4804
  ${countBy(findings.flatMap((finding) => finding.weakPractices)).slice(0, 12).map(([practice, count]) => `- ${practice} (${count})`).join("\n")}
4299
4805
  `;
4300
- const overviewPath = join18(cwd, "research", "summaries", "scan-overview.md");
4806
+ const overviewPath = join21(cwd, "research", "summaries", "scan-overview.md");
4301
4807
  writeText(overviewPath, overview);
4302
4808
  outputs.push(overviewPath);
4303
4809
  for (const [target, config] of Object.entries(SUMMARY_TARGETS)) {
4304
4810
  const categories = config.categories;
4305
4811
  const scopedFindings = findings.filter((finding) => categories.includes(finding.category));
4306
- const path = join18(cwd, "research", "summaries", `${target}.md`);
4812
+ const path = join21(cwd, "research", "summaries", `${target}.md`);
4307
4813
  const summary2 = `# ${config.title}
4308
4814
 
4309
4815
  Generated from ${scopedFindings.length} relevant repository findings.
@@ -4344,7 +4850,7 @@ Review the generated research summaries, then convert repeated best practices in
4344
4850
 
4345
4851
  Do not copy source code from scanned repositories. Adopt only generalized practices with clear rationale.
4346
4852
  `;
4347
- const path = join18(cwd, "research", "proposed-updates.md");
4853
+ const path = join21(cwd, "research", "proposed-updates.md");
4348
4854
  writeText(path, output);
4349
4855
  return path;
4350
4856
  }
@@ -4461,8 +4967,9 @@ function proposeCorrectionUpstream(cwd, id) {
4461
4967
  }
4462
4968
 
4463
4969
  // src/studio/session.ts
4464
- import { existsSync as existsSync18, readFileSync as readFileSync17, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
4465
- 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";
4466
4973
  function sessionDir(sessionId) {
4467
4974
  return `${COUNCIL_SESSIONS_DIR}/${safeSlug(sessionId)}`;
4468
4975
  }
@@ -4478,6 +4985,9 @@ function indexPath(sessionId) {
4478
4985
  function transcriptPath(sessionId) {
4479
4986
  return `${sessionDir(sessionId)}/transcript.md`;
4480
4987
  }
4988
+ function sessionLockPath(sessionId) {
4989
+ return `${sessionDir(sessionId)}/.session.lock`;
4990
+ }
4481
4991
  function readDefaultWorkflowOutputs(cwd, workflowId) {
4482
4992
  const roster = readJsonFile(cwd, DEFAULT_AGENT_ROSTER_TARGET);
4483
4993
  const workflow = roster?.workflows?.find((item) => item.id === workflowId);
@@ -4489,7 +4999,7 @@ function readDefaultWorkflowOutputs(cwd, workflowId) {
4489
4999
  function startSession(cwd, options) {
4490
5000
  ensureStudioDirs(cwd);
4491
5001
  const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4492
- const sessionId = safeSlug(`${datePrefix}-${options.title}`);
5002
+ const sessionId = safeSlug(`${datePrefix}-${options.title}-${randomUUID2().slice(0, 8)}`);
4493
5003
  const now = nowIso();
4494
5004
  const workflowId = options.workflowId ?? "planning";
4495
5005
  const session2 = {
@@ -4519,9 +5029,9 @@ function startSession(cwd, options) {
4519
5029
  return { sessionId, sessionPath: sessionDir(sessionId) };
4520
5030
  }
4521
5031
  function listSessions(cwd) {
4522
- const root = join19(cwd, COUNCIL_SESSIONS_DIR);
4523
- if (!existsSync18(root)) return [];
4524
- 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));
4525
5035
  }
4526
5036
  function getActiveSessionId(cwd) {
4527
5037
  const active = readTextFile(cwd, ACTIVE_SESSION_FILE)?.trim();
@@ -4547,7 +5057,18 @@ function writeSession(cwd, session2) {
4547
5057
  writeJsonFile(cwd, sessionJsonPath(session2.sessionId), StudioSessionContract.parse(session2));
4548
5058
  }
4549
5059
  function appendSessionEvent(cwd, sessionId, event) {
4550
- 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
+ );
4551
5072
  appendJsonLine(cwd, eventsPath(sessionId), parsed);
4552
5073
  const session2 = readSession(cwd, sessionId);
4553
5074
  const updated = {
@@ -4640,22 +5161,24 @@ function recordRequiredOutput(cwd, name, status, evidence) {
4640
5161
  const trimmedName = name.trim();
4641
5162
  if (!trimmedName) throw new Error("Required output name is required.");
4642
5163
  const sessionId = getActiveSessionId(cwd);
4643
- const session2 = readSession(cwd, sessionId);
4644
- const now = nowIso();
4645
- const output = {
4646
- name: trimmedName,
4647
- status,
4648
- ...evidence ? { evidence: redactSensitive(evidence) } : {}
4649
- };
4650
- const outputIndex = session2.requiredOutputs.findIndex((item) => item.name === trimmedName);
4651
- const requiredOutputs = outputIndex === -1 ? [...session2.requiredOutputs, output] : session2.requiredOutputs.map((item, index) => index === outputIndex ? { ...item, ...output } : item);
4652
- writeSession(cwd, { ...session2, requiredOutputs, updatedAt: now });
4653
- return appendSessionEvent(cwd, sessionId, {
4654
- type: "required_output_updated",
4655
- createdAt: now,
4656
- outputName: trimmedName,
4657
- outputStatus: status,
4658
- ...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
+ });
4659
5182
  });
4660
5183
  }
4661
5184
  function closeSession(cwd, status) {
@@ -4672,14 +5195,16 @@ function renderActiveSession(cwd) {
4672
5195
  return renderSession(cwd, getActiveSessionId(cwd));
4673
5196
  }
4674
5197
  function renderSession(cwd, sessionId) {
4675
- const session2 = readSession(cwd, sessionId);
4676
- const events = readSessionEvents(cwd, sessionId);
4677
- const renderedAt = nowIso();
4678
- const updated = { ...session2, renderedAt, updatedAt: renderedAt };
4679
- writeTextFile(cwd, indexPath(sessionId), renderSessionIndex(updated, events));
4680
- writeTextFile(cwd, transcriptPath(sessionId), renderSessionTranscript(updated, events));
4681
- writeSession(cwd, updated);
4682
- 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
+ });
4683
5208
  }
4684
5209
  function renderSessionIndex(session2, events) {
4685
5210
  const handoffs = events.filter((event) => event.type === "handoff");
@@ -4738,7 +5263,7 @@ ${verification.map((event) => `| ${escapeMarkdownTableCell(event.command)} | ${e
4738
5263
  ## Next Actions
4739
5264
 
4740
5265
  ${renderNextActions(session2, verification)}
4741
- `;
5266
+ `.trimEnd() + "\n";
4742
5267
  }
4743
5268
  function renderDecisionRow(event) {
4744
5269
  if (event.type === "handoff") {
@@ -4793,7 +5318,7 @@ ${rows.join("\n")}`;
4793
5318
  Generated from \`${sessionDir(session2.sessionId)}/events.jsonl\`.
4794
5319
 
4795
5320
  ${sections || "No events recorded."}
4796
- `;
5321
+ `.trimEnd() + "\n";
4797
5322
  }
4798
5323
 
4799
5324
  // src/studio/export.ts
@@ -5056,19 +5581,19 @@ function safeJsonForHtml(value) {
5056
5581
  }
5057
5582
 
5058
5583
  // src/studio/setup-browser.ts
5059
- import { execFileSync } from "child_process";
5584
+ import { execFileSync as execFileSync2 } from "child_process";
5060
5585
  function openBrowser(url) {
5061
5586
  const platform = process.platform;
5062
5587
  try {
5063
5588
  if (platform === "darwin") {
5064
- execFileSync("open", [url], { stdio: "ignore" });
5589
+ execFileSync2("open", [url], { stdio: "ignore" });
5065
5590
  return;
5066
5591
  }
5067
5592
  if (platform === "win32") {
5068
- execFileSync("cmd", ["/c", "start", "", url], { stdio: "ignore" });
5593
+ execFileSync2("cmd", ["/c", "start", "", url], { stdio: "ignore" });
5069
5594
  return;
5070
5595
  }
5071
- execFileSync("xdg-open", [url], { stdio: "ignore" });
5596
+ execFileSync2("xdg-open", [url], { stdio: "ignore" });
5072
5597
  } catch {
5073
5598
  console.log(`Open this URL in your browser: ${url}`);
5074
5599
  }
@@ -5080,10 +5605,10 @@ async function promptStartSetup(defaultYes = true) {
5080
5605
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
5081
5606
  const rl = createInterface({ input: process.stdin, output: process.stdout });
5082
5607
  const question = defaultYes ? "Start setup wizard now? [Y/n] " : "Start setup wizard now? [y/N] ";
5083
- const answer = await new Promise((resolve3) => {
5608
+ const answer = await new Promise((resolve4) => {
5084
5609
  rl.question(question, (value) => {
5085
5610
  rl.close();
5086
- resolve3(value.trim().toLowerCase());
5611
+ resolve4(value.trim().toLowerCase());
5087
5612
  });
5088
5613
  });
5089
5614
  if (!answer) return defaultYes;
@@ -5202,8 +5727,8 @@ function parseSetupFormPayload(raw) {
5202
5727
  }
5203
5728
 
5204
5729
  // src/studio/wizard/checklist.ts
5205
- import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
5206
- import { join as join20 } from "path";
5730
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
5731
+ import { join as join23 } from "path";
5207
5732
  var IDE_PATHS = {
5208
5733
  cursor: ".cursor/agents/planner.md",
5209
5734
  copilot: ".github/copilot-instructions.md",
@@ -5224,12 +5749,12 @@ function saveIdeChecklist(cwd, ideSurface) {
5224
5749
  function detectIdeRulePresent(cwd, ideSurface) {
5225
5750
  const rel = IDE_PATHS[ideSurface];
5226
5751
  if (ideSurface === "cursor") {
5227
- 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"));
5228
5753
  }
5229
5754
  if (rel.endsWith("/")) {
5230
- return existsSync19(join20(cwd, rel));
5755
+ return existsSync22(join23(cwd, rel));
5231
5756
  }
5232
- return existsSync19(join20(cwd, rel));
5757
+ return existsSync22(join23(cwd, rel));
5233
5758
  }
5234
5759
  var VISUAL_QA_MARKER = "## Visual QA Tier";
5235
5760
  var VISUAL_QA_BLOCKS = {
@@ -5257,11 +5782,11 @@ This project uses the **Mature** visual QA tier.
5257
5782
  };
5258
5783
  function writeVisualQaTier(cwd, tier) {
5259
5784
  const path = "TESTING.md";
5260
- const fullPath = join20(cwd, path);
5261
- if (!existsSync19(fullPath)) {
5785
+ const fullPath = join23(cwd, path);
5786
+ if (!existsSync22(fullPath)) {
5262
5787
  return { updated: false, path, reason: "TESTING.md not found in project root." };
5263
5788
  }
5264
- const current = readFileSync18(fullPath, "utf8");
5789
+ const current = readFileSync20(fullPath, "utf8");
5265
5790
  if (current.includes(VISUAL_QA_MARKER)) {
5266
5791
  return {
5267
5792
  updated: false,
@@ -5327,7 +5852,7 @@ function renderAgentBriefsMarkdown(cwd, file) {
5327
5852
  const text = file.briefs[agent.id]?.trim();
5328
5853
  if (!text) continue;
5329
5854
  wroteAny = true;
5330
- lines.push(`## ${agent.name}`, "", `**Role:** ${agent.roleSummary}`, "", text, "");
5855
+ lines.push(`## ${escapeMarkdownText(agent.name)}`, "", `**Role:** ${escapeMarkdownText(agent.roleSummary)}`, "", escapeMarkdownText(text), "");
5331
5856
  }
5332
5857
  if (!wroteAny) {
5333
5858
  lines.push("_No agent briefs recorded yet. Run `agent-kit setup` to brief your team._", "");
@@ -5437,8 +5962,8 @@ function extractSetupFormFromWizardForm(form) {
5437
5962
  }
5438
5963
 
5439
5964
  // src/studio/wizard/drafts.ts
5440
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
5441
- import { join as join21 } from "path";
5965
+ import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5966
+ import { join as join24 } from "path";
5442
5967
  var DESIGN_DRAFT_JSON = ".agent-kit/onboarding/design-draft.json";
5443
5968
  var MESSAGING_DRAFT_JSON = ".agent-kit/onboarding/messaging-draft.json";
5444
5969
  function loadDesignDraft(cwd) {
@@ -5464,12 +5989,12 @@ function previewDesignMarkdown(draft) {
5464
5989
 
5465
5990
  | Area | Wizard draft |
5466
5991
  | --- | --- |
5467
- | Primary audience | ${draft.audience.trim() || "TBD"} |
5468
- | Content inventory | ${draft.contentInventory.trim() || "TBD"} |
5992
+ | Primary audience | ${escapeMarkdownTableCell(draft.audience.trim() || "TBD")} |
5993
+ | Content inventory | ${escapeMarkdownTableCell(draft.contentInventory.trim() || "TBD")} |
5469
5994
 
5470
5995
  ## Anti-References (wizard draft)
5471
5996
 
5472
- ${draft.antiReferences.trim() || "- TBD: pattern to avoid."}
5997
+ ${escapeMarkdownText(draft.antiReferences.trim() || "- TBD: pattern to avoid.")}
5473
5998
  `;
5474
5999
  }
5475
6000
  function previewMessagingMarkdown(draft) {
@@ -5477,24 +6002,29 @@ function previewMessagingMarkdown(draft) {
5477
6002
 
5478
6003
  | Question | Current Answer |
5479
6004
  | --- | --- |
5480
- | Who is the primary audience? | ${draft.audience.trim() || "TBD"} |
5481
- | What painful problem do they need solved? | ${draft.pain.trim() || "TBD"} |
5482
- | 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")} |
5483
6008
  `;
5484
6009
  }
5485
6010
  function appendSectionToDoc(cwd, doc, sectionMarkdown) {
5486
- const fullPath = join21(cwd, doc);
5487
- if (!existsSync20(fullPath)) {
6011
+ const fullPath = join24(cwd, doc);
6012
+ if (!existsSync23(fullPath)) {
5488
6013
  return { target: doc, action: "missing" };
5489
6014
  }
5490
- const current = readFileSync19(fullPath, "utf8");
5491
- if (current.includes("(wizard draft)")) {
5492
- return { target: doc, action: "conflict", conflictPath: `.agent-kit/conflicts/wizard-${doc}` };
5493
- }
5494
- writeTextFile(cwd, doc, `${current.trimEnd()}
6015
+ const current = readFileSync21(fullPath, "utf8");
6016
+ const proposed = `${current.trimEnd()}
5495
6017
 
5496
6018
  ${sectionMarkdown.trim()}
5497
- `);
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);
5498
6028
  return { target: doc, action: "appended" };
5499
6029
  }
5500
6030
  function applyDesignDraft(cwd) {
@@ -5526,8 +6056,8 @@ function applyDrafts(cwd) {
5526
6056
  }
5527
6057
 
5528
6058
  // src/studio/office/render.ts
5529
- import { readFileSync as readFileSync20 } from "fs";
5530
- import { join as join22 } from "path";
6059
+ import { readFileSync as readFileSync22 } from "fs";
6060
+ import { join as join25 } from "path";
5531
6061
 
5532
6062
  // src/studio/office/map.ts
5533
6063
  var MAP_WIDTH = 28;
@@ -5649,12 +6179,12 @@ var PRODUCT_CATEGORIES = ["content-app", "saas", "admin", "marketplace", "tool",
5649
6179
  var TENANT_MODELS = ["single-user", "team", "tenant", "marketplace", "admin", "public-content"];
5650
6180
  function readOfficeAsset(name) {
5651
6181
  const root = findPackageRoot();
5652
- const distPath = join22(root, "dist", "studio", "office", "assets", name);
5653
- 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);
5654
6184
  try {
5655
- return readFileSync20(distPath, "utf8");
6185
+ return readFileSync22(distPath, "utf8");
5656
6186
  } catch {
5657
- return readFileSync20(srcPath, "utf8");
6187
+ return readFileSync22(srcPath, "utf8");
5658
6188
  }
5659
6189
  }
5660
6190
  function buildOfficeBootConfig(cwd, viewModel) {
@@ -5699,6 +6229,51 @@ function renderOfficeHtml(boot, mode) {
5699
6229
  const isStudio = mode === "studio";
5700
6230
  const title = isStudio ? "Agent Kit \u2014 Live Studio" : "Agent Kit \u2014 Setup Office";
5701
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>`;
5702
6277
  return `<!doctype html>
5703
6278
  <html lang="en">
5704
6279
  <head>
@@ -5740,7 +6315,7 @@ function renderOfficeHtml(boot, mode) {
5740
6315
  <div id="nameplate-layer" class="nameplate-layer" aria-hidden="true"></div>
5741
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>
5742
6317
  </div>
5743
- ${isStudio ? '<aside class="transcript-panel" id="transcript-panel" aria-label="Session transcript"><div class="studio-controls" id="studio-controls"><label class="studio-label" for="session-picker">Session</label><select id="session-picker" aria-label="Council session"></select><form id="studio-note-form" class="studio-note-form"><select id="studio-note-agent" aria-label="Agent for note"></select><input id="studio-note-text" type="text" maxlength="3999" placeholder="Add council note\u2026" /><button type="submit" class="btn secondary">Add note</button></form><button type="button" class="btn primary" id="studio-render-btn">Render markdown</button></div><h2>Transcript</h2><ol id="transcript-list"></ol></aside>' : ""}
6318
+ ${isStudio ? studioAside : ""}
5744
6319
  </main>
5745
6320
  <div id="status" class="status" role="status" aria-live="polite"></div>
5746
6321
  <div id="depth-modal" class="modal modal-blur" hidden>
@@ -5826,18 +6401,18 @@ function allAgentBriefsComplete(form, agentIds) {
5826
6401
  }
5827
6402
 
5828
6403
  // src/studio/wizard/render.ts
5829
- import { readFileSync as readFileSync21 } from "fs";
5830
- import { join as join23 } from "path";
6404
+ import { readFileSync as readFileSync23 } from "fs";
6405
+ import { join as join26 } from "path";
5831
6406
  var PRODUCT_CATEGORIES2 = ["content-app", "saas", "admin", "marketplace", "tool", "ecommerce", "portfolio", "education", "community", "ai-workflow", "other"];
5832
6407
  var TENANT_MODELS2 = ["single-user", "team", "tenant", "marketplace", "admin", "public-content"];
5833
6408
  function readWizardAsset(name) {
5834
6409
  const root = findPackageRoot();
5835
- const distPath = join23(root, "dist", "studio", "wizard", "assets", name);
5836
- 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);
5837
6412
  try {
5838
- return readFileSync21(distPath, "utf8");
6413
+ return readFileSync23(distPath, "utf8");
5839
6414
  } catch {
5840
- return readFileSync21(srcPath, "utf8");
6415
+ return readFileSync23(srcPath, "utf8");
5841
6416
  }
5842
6417
  }
5843
6418
  function mergeWizardSteps(cwd) {
@@ -5927,12 +6502,12 @@ function renderSetupWizardHtmlWithContext(cwd) {
5927
6502
  }
5928
6503
 
5929
6504
  // src/studio/agentic-level.ts
5930
- import { existsSync as existsSync21, readFileSync as readFileSync22 } from "fs";
5931
- import { join as join24 } from "path";
6505
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "fs";
6506
+ import { join as join27 } from "path";
5932
6507
  var CACHE_TTL_MS = 3e4;
5933
6508
  var cache = /* @__PURE__ */ new Map();
5934
6509
  function isMaintainerSourceRepo(cwd) {
5935
- 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"));
5936
6511
  }
5937
6512
  function signal(id, level, label, pass, evidence, remediation) {
5938
6513
  return { id, level, label, pass, evidence, remediation };
@@ -5948,23 +6523,23 @@ function detectIdePresent(cwd) {
5948
6523
  return { pass: true, evidence: `${surface} adapter files detected` };
5949
6524
  }
5950
6525
  }
5951
- if (existsSync21(join24(cwd, ".cursor/rules/cursor-agent-kit.mdc"))) {
6526
+ if (existsSync24(join27(cwd, ".cursor/rules/cursor-agent-kit.mdc"))) {
5952
6527
  return { pass: true, evidence: "Cursor council rules from init" };
5953
6528
  }
5954
6529
  return { pass: false, evidence: "No IDE adapter rules or subagents detected" };
5955
6530
  }
5956
6531
  function detectTierBSubagents(cwd) {
5957
6532
  const paths = [".cursor/agents/planner.md", ".codex/agents/planner.toml", ".claude/agents/planner.md", ".github/copilot-instructions.md"];
5958
- const found = paths.filter((rel) => existsSync21(join24(cwd, rel)));
6533
+ const found = paths.filter((rel) => existsSync24(join27(cwd, rel)));
5959
6534
  if (found.length > 0) {
5960
6535
  return { pass: true, evidence: `Specialist surface: ${found[0]}` };
5961
6536
  }
5962
6537
  return { pass: false, evidence: "No council subagents or Copilot instructions installed" };
5963
6538
  }
5964
6539
  function readDocSnippet(cwd, name, needles) {
5965
- const path = join24(cwd, name);
5966
- if (!existsSync21(path)) return false;
5967
- 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();
5968
6543
  return needles.every((needle) => lower.includes(needle.toLowerCase()));
5969
6544
  }
5970
6545
  function adapterTargetForIde(ide) {
@@ -5992,8 +6567,8 @@ function buildSignals(cwd, maintainerProfile) {
5992
6567
  "l4-agents-md",
5993
6568
  4,
5994
6569
  "Council contract (AGENTS.md)",
5995
- existsSync21(join24(cwd, "AGENTS.md")),
5996
- 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",
5997
6572
  "Run agent-kit init --stack next-supabase"
5998
6573
  )
5999
6574
  );
@@ -6002,8 +6577,8 @@ function buildSignals(cwd, maintainerProfile) {
6002
6577
  "l4-adapters-doc",
6003
6578
  4,
6004
6579
  "Assistant activation doc",
6005
- existsSync21(join24(cwd, "ASSISTANT_ADAPTERS.md")),
6006
- 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",
6007
6582
  "Run agent-kit init or agent-kit update"
6008
6583
  )
6009
6584
  );
@@ -6012,8 +6587,8 @@ function buildSignals(cwd, maintainerProfile) {
6012
6587
  "l4-roster",
6013
6588
  4,
6014
6589
  "Machine-readable council roster",
6015
- existsSync21(join24(cwd, ".agent-kit/agent-roster.json")),
6016
- 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",
6017
6592
  "Run agent-kit init or agent-kit update"
6018
6593
  )
6019
6594
  );
@@ -6031,7 +6606,7 @@ function buildSignals(cwd, maintainerProfile) {
6031
6606
  signals.push(
6032
6607
  signal("l5-subagents", 5, "Tier-B specialist activation", tierB.pass, tierB.evidence, "Run agent-kit init --activate cursor|codex|claude|copilot")
6033
6608
  );
6034
- const loopCoding = existsSync21(join24(cwd, "LOOP_CODING.md"));
6609
+ const loopCoding = existsSync24(join27(cwd, "LOOP_CODING.md"));
6035
6610
  signals.push(
6036
6611
  signal(
6037
6612
  "l6-loop-coding",
@@ -6062,12 +6637,12 @@ function buildSignals(cwd, maintainerProfile) {
6062
6637
  )
6063
6638
  );
6064
6639
  if (maintainerProfile) {
6065
- const pkgPath = join24(cwd, "package.json");
6640
+ const pkgPath = join27(cwd, "package.json");
6066
6641
  let releaseCheck = false;
6067
- if (existsSync21(pkgPath)) {
6642
+ if (existsSync24(pkgPath)) {
6068
6643
  try {
6069
- const pkg = JSON.parse(readFileSync22(pkgPath, "utf8"));
6070
- 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"));
6071
6646
  } catch {
6072
6647
  releaseCheck = false;
6073
6648
  }
@@ -6082,7 +6657,7 @@ function buildSignals(cwd, maintainerProfile) {
6082
6657
  "Use npm run release:check before merge; see MAINTAINER_RELEASE.md"
6083
6658
  )
6084
6659
  );
6085
- 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"]);
6086
6661
  signals.push(
6087
6662
  signal(
6088
6663
  "l6-maintainer-docs",
@@ -6110,8 +6685,8 @@ function buildSignals(cwd, maintainerProfile) {
6110
6685
  signals.push(
6111
6686
  signal("l6-adapter-validate", 6, "Adapter validate for active IDE", adapterPass, adapterEvidence, "Run agent-kit adapter validate cursor|codex|all")
6112
6687
  );
6113
- const ciWorkflow = existsSync21(join24(cwd, ".github/workflows/agent-kit-audit.yml"));
6114
- 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"]);
6115
6690
  const evalLoop = ciWorkflow || testingEval;
6116
6691
  signals.push(
6117
6692
  signal(
@@ -6192,23 +6767,138 @@ function invalidateAgenticLevelCache(cwd) {
6192
6767
  cache.delete(cwd);
6193
6768
  }
6194
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
+
6195
6883
  // src/studio/setup-server.ts
6196
6884
  var DEFAULT_PORT = 9321;
6197
6885
  var DEFAULT_HOST = "127.0.0.1";
6198
6886
  function sendJson(response, statusCode, payload) {
6199
6887
  response.writeHead(statusCode, {
6888
+ ...baseSecurityHeaders(),
6200
6889
  "Content-Type": "application/json; charset=utf-8",
6201
- "Cache-Control": "no-store"
6890
+ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'"
6202
6891
  });
6203
6892
  response.end(JSON.stringify(payload));
6204
6893
  }
6205
- function sendHtml(response, html) {
6894
+ function sendHtml(response, html, security) {
6895
+ const secured = secureLocalHtml(html, security);
6206
6896
  response.writeHead(200, {
6897
+ ...baseSecurityHeaders(),
6207
6898
  "Content-Type": "text/html; charset=utf-8",
6208
- "Cache-Control": "no-store",
6209
- "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
6210
6900
  });
6211
- response.end(html);
6901
+ response.end(secured.body);
6212
6902
  }
6213
6903
  function buildStatePayload(cwd, options = {}) {
6214
6904
  ensureProjectContextForSetup(cwd);
@@ -6237,7 +6927,7 @@ function buildStatePayload(cwd, options = {}) {
6237
6927
  };
6238
6928
  }
6239
6929
  function sendRedirect(response, location) {
6240
- response.writeHead(302, { Location: location, "Cache-Control": "no-store" });
6930
+ response.writeHead(302, { ...baseSecurityHeaders(), Location: location });
6241
6931
  response.end();
6242
6932
  }
6243
6933
  function findOfficeStation(cwd, stationId) {
@@ -6256,18 +6946,27 @@ function markOfficeSectionComplete(cwd, stationId, form) {
6256
6946
  }
6257
6947
  markSectionComplete(cwd, section);
6258
6948
  }
6259
- 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
+ }
6260
6959
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
6261
6960
  if (request.method === "GET" && url.pathname === "/setup/wizard") {
6262
6961
  sendRedirect(response, "/wizard");
6263
6962
  return;
6264
6963
  }
6265
6964
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office" || url.pathname === "/setup")) {
6266
- sendHtml(response, renderSetupOfficeHtmlWithContext(cwd));
6965
+ sendHtml(response, renderSetupOfficeHtmlWithContext(cwd), security);
6267
6966
  return;
6268
6967
  }
6269
6968
  if (request.method === "GET" && url.pathname === "/wizard") {
6270
- sendHtml(response, renderSetupWizardHtmlWithContext(cwd));
6969
+ sendHtml(response, renderSetupWizardHtmlWithContext(cwd), security);
6271
6970
  return;
6272
6971
  }
6273
6972
  if (request.method === "GET" && url.pathname === "/api/state") {
@@ -6436,7 +7135,7 @@ async function handleRequest(cwd, request, response) {
6436
7135
  sendJson(response, 404, { error: "Not found." });
6437
7136
  }
6438
7137
  function listen(server, host, port) {
6439
- return new Promise((resolve3, reject) => {
7138
+ return new Promise((resolve4, reject) => {
6440
7139
  server.once("error", reject);
6441
7140
  server.listen(port, host, () => {
6442
7141
  const address = server.address();
@@ -6444,17 +7143,18 @@ function listen(server, host, port) {
6444
7143
  reject(new Error("Could not determine setup server port."));
6445
7144
  return;
6446
7145
  }
6447
- resolve3(address.port);
7146
+ resolve4(address.port);
6448
7147
  });
6449
7148
  });
6450
7149
  }
6451
7150
  async function startSetupServer(options) {
6452
7151
  const host = options.host ?? DEFAULT_HOST;
7152
+ const security = createLocalHttpSecurity(host);
6453
7153
  const requestedPort = options.port ?? DEFAULT_PORT;
6454
7154
  ensureProjectContextForSetup(options.cwd);
6455
7155
  loadOnboardingState(options.cwd);
6456
7156
  const server = createServer((request, response) => {
6457
- handleRequest(options.cwd, request, response).catch((error) => {
7157
+ handleRequest(options.cwd, request, response, security).catch((error) => {
6458
7158
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
6459
7159
  });
6460
7160
  });
@@ -6470,16 +7170,18 @@ async function startSetupServer(options) {
6470
7170
  throw error;
6471
7171
  }
6472
7172
  }
7173
+ security.port = port;
6473
7174
  return {
6474
- url: `http://${host}:${port}`,
7175
+ url: formatLocalUrl(host, port),
6475
7176
  port,
6476
7177
  requestedPort,
6477
7178
  portFallback,
6478
7179
  defaultView: "office",
6479
- close: () => new Promise((resolve3, reject) => {
7180
+ csrfToken: security.csrfToken,
7181
+ close: () => new Promise((resolve4, reject) => {
6480
7182
  server.close((closeError) => {
6481
7183
  if (closeError) reject(closeError);
6482
- else resolve3();
7184
+ else resolve4();
6483
7185
  });
6484
7186
  })
6485
7187
  };
@@ -6488,79 +7190,135 @@ async function startSetupServer(options) {
6488
7190
  // src/studio/studio-server.ts
6489
7191
  import { watch } from "fs";
6490
7192
  import { createServer as createServer2 } from "http";
6491
- import { join as join25 } from "path";
7193
+ import { join as join28 } from "path";
6492
7194
  var DEFAULT_PORT2 = 9331;
6493
7195
  var DEFAULT_HOST2 = "127.0.0.1";
6494
- var sseClients = /* @__PURE__ */ new Set();
6495
- var activeWatcher = null;
6496
- 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
+ };
6497
7271
  function sendJson2(response, statusCode, payload) {
6498
7272
  response.writeHead(statusCode, {
7273
+ ...baseSecurityHeaders(),
6499
7274
  "Content-Type": "application/json; charset=utf-8",
6500
- "Cache-Control": "no-store"
7275
+ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'"
6501
7276
  });
6502
7277
  response.end(JSON.stringify(payload));
6503
7278
  }
6504
- function sendHtml2(response, html) {
7279
+ function sendHtml2(response, html, security) {
7280
+ const secured = secureLocalHtml(html, security);
6505
7281
  response.writeHead(200, {
7282
+ ...baseSecurityHeaders(),
6506
7283
  "Content-Type": "text/html; charset=utf-8",
6507
- "Cache-Control": "no-store",
6508
- "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
6509
7285
  });
6510
- response.end(html);
6511
- }
6512
- function broadcastSse(event, data) {
6513
- const payload = `event: ${event}
6514
- data: ${JSON.stringify(data)}
6515
-
6516
- `;
6517
- for (const client of sseClients) {
6518
- try {
6519
- client.write(payload);
6520
- } catch {
6521
- sseClients.delete(client);
6522
- }
6523
- }
6524
- }
6525
- function stopWatcher() {
6526
- if (activeWatcher) {
6527
- activeWatcher.close();
6528
- activeWatcher = null;
6529
- watchedEventsPath = null;
6530
- }
6531
- }
6532
- function watchSessionEvents(cwd, sessionId) {
6533
- const eventsPath2 = join25(cwd, COUNCIL_SESSIONS_DIR, sessionId, "events.jsonl");
6534
- if (watchedEventsPath === eventsPath2 && activeWatcher) return;
6535
- stopWatcher();
6536
- watchedEventsPath = eventsPath2;
6537
- try {
6538
- activeWatcher = watch(eventsPath2, () => {
6539
- try {
6540
- const events = readSessionEvents(cwd, sessionId);
6541
- const latest = events.at(-1);
6542
- if (latest) broadcastSse("event", { sessionId, event: latest, total: events.length });
6543
- } catch {
6544
- }
6545
- });
6546
- } catch {
6547
- watchedEventsPath = null;
6548
- activeWatcher = null;
6549
- }
7286
+ response.end(secured.body);
6550
7287
  }
6551
7288
  function safeSessionId(raw) {
6552
7289
  if (!/^[a-z0-9-]+$/i.test(raw)) return null;
6553
7290
  return raw;
6554
7291
  }
6555
- function handleRequest2(cwd, request, response) {
6556
- handleRequestAsync(cwd, request, response).catch((error) => {
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.");
7300
+ }
7301
+ throw error;
7302
+ }
7303
+ }
7304
+ function handleRequest2(cwd, request, response, security, eventHub) {
7305
+ handleRequestAsync(cwd, request, response, security, eventHub).catch((error) => {
6557
7306
  sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
6558
7307
  });
6559
7308
  }
6560
- async function handleRequestAsync(cwd, request, response) {
7309
+ async function handleRequestAsync(cwd, request, response, security, eventHub) {
7310
+ try {
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;
7318
+ }
6561
7319
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
6562
7320
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office")) {
6563
- sendHtml2(response, renderLiveStudioHtmlWithContext(cwd));
7321
+ sendHtml2(response, renderLiveStudioHtmlWithContext(cwd), security);
6564
7322
  return;
6565
7323
  }
6566
7324
  if (request.method === "GET" && url.pathname === "/api/sessions") {
@@ -6575,6 +7333,121 @@ async function handleRequestAsync(cwd, request, response) {
6575
7333
  sendJson2(response, 200, { activeSessionId, sessions });
6576
7334
  return;
6577
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
+ }
6578
7451
  const eventsMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/events$/);
6579
7452
  if (request.method === "GET" && eventsMatch) {
6580
7453
  const sessionId = safeSessionId(eventsMatch[1] ?? "");
@@ -6607,13 +7480,14 @@ async function handleRequestAsync(cwd, request, response) {
6607
7480
  return;
6608
7481
  }
6609
7482
  response.writeHead(200, {
7483
+ ...baseSecurityHeaders(),
6610
7484
  "Content-Type": "text/event-stream; charset=utf-8",
6611
- "Cache-Control": "no-store",
6612
- Connection: "keep-alive"
7485
+ "Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'",
7486
+ Connection: "keep-alive",
7487
+ "X-Accel-Buffering": "no"
6613
7488
  });
6614
7489
  response.write(": connected\n\n");
6615
- sseClients.add(response);
6616
- watchSessionEvents(cwd, activeId);
7490
+ const unsubscribe = eventHub.subscribe(activeId, response);
6617
7491
  try {
6618
7492
  const events = readSessionEvents(cwd, activeId);
6619
7493
  response.write(`event: snapshot
@@ -6627,8 +7501,7 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
6627
7501
  `);
6628
7502
  }
6629
7503
  request.on("close", () => {
6630
- sseClients.delete(response);
6631
- if (sseClients.size === 0) stopWatcher();
7504
+ unsubscribe();
6632
7505
  });
6633
7506
  return;
6634
7507
  }
@@ -6656,7 +7529,7 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
6656
7529
  return;
6657
7530
  }
6658
7531
  const event = recordSessionNote(cwd, sessionId, agent, text);
6659
- broadcastSse("event", { sessionId, event, total: readSessionEvents(cwd, sessionId).length });
7532
+ eventHub.broadcast(sessionId, event, readSessionEvents(cwd, sessionId).length);
6660
7533
  sendJson2(response, 200, { event });
6661
7534
  } catch (error) {
6662
7535
  sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
@@ -6686,7 +7559,7 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
6686
7559
  sendJson2(response, 404, { error: "Not found." });
6687
7560
  }
6688
7561
  function listen2(server, host, port) {
6689
- return new Promise((resolve3, reject) => {
7562
+ return new Promise((resolve4, reject) => {
6690
7563
  server.once("error", reject);
6691
7564
  server.listen(port, host, () => {
6692
7565
  const address = server.address();
@@ -6694,17 +7567,19 @@ function listen2(server, host, port) {
6694
7567
  reject(new Error("Could not determine studio server port."));
6695
7568
  return;
6696
7569
  }
6697
- resolve3(address.port);
7570
+ resolve4(address.port);
6698
7571
  });
6699
7572
  });
6700
7573
  }
6701
7574
  async function startStudioServer(options) {
6702
7575
  const host = options.host ?? DEFAULT_HOST2;
7576
+ const security = createLocalHttpSecurity(host);
6703
7577
  const requestedPort = options.port ?? DEFAULT_PORT2;
6704
7578
  ensureStudioDirs(options.cwd);
7579
+ const eventHub = new StudioEventHub(options.cwd);
6705
7580
  const server = createServer2((request, response) => {
6706
7581
  try {
6707
- handleRequest2(options.cwd, request, response);
7582
+ handleRequest2(options.cwd, request, response, security, eventHub);
6708
7583
  } catch (error) {
6709
7584
  sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
6710
7585
  }
@@ -6721,31 +7596,26 @@ async function startStudioServer(options) {
6721
7596
  throw error;
6722
7597
  }
6723
7598
  }
7599
+ security.port = port;
6724
7600
  return {
6725
- url: `http://${host}:${port}`,
7601
+ url: formatLocalUrl(host, port),
6726
7602
  port,
6727
7603
  requestedPort,
6728
7604
  portFallback,
6729
- close: () => new Promise((resolve3, reject) => {
6730
- stopWatcher();
6731
- for (const client of sseClients) {
6732
- try {
6733
- client.end();
6734
- } catch {
6735
- }
6736
- }
6737
- sseClients.clear();
7605
+ csrfToken: security.csrfToken,
7606
+ close: () => new Promise((resolve4, reject) => {
7607
+ eventHub.close();
6738
7608
  server.close((closeError) => {
6739
7609
  if (closeError) reject(closeError);
6740
- else resolve3();
7610
+ else resolve4();
6741
7611
  });
6742
7612
  })
6743
7613
  };
6744
7614
  }
6745
7615
 
6746
7616
  // src/studio/session-checkpoint.ts
6747
- import { existsSync as existsSync22, readFileSync as readFileSync23 } from "fs";
6748
- 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";
6749
7619
  function parseCheckpointMarkdown(content) {
6750
7620
  const payload = { notes: [], decisions: [], handoffs: [], outputs: [] };
6751
7621
  const sections = content.split(/^## /m).slice(1);
@@ -6801,7 +7671,7 @@ function parseCheckpointMarkdown(content) {
6801
7671
  return payload;
6802
7672
  }
6803
7673
  function parseCheckpointFile(filePath) {
6804
- const content = readFileSync23(filePath, "utf8");
7674
+ const content = readFileSync25(filePath, "utf8");
6805
7675
  const ext = extname(filePath).toLowerCase();
6806
7676
  if (ext === ".json") {
6807
7677
  const parsed = JSON.parse(content);
@@ -6877,8 +7747,8 @@ function applySessionCheckpoint(cwd, payload) {
6877
7747
  };
6878
7748
  }
6879
7749
  function checkpointSessionFromFile(cwd, filePath) {
6880
- const absolute = join26(cwd, filePath);
6881
- 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}`);
6882
7752
  return applySessionCheckpoint(cwd, parseCheckpointFile(absolute));
6883
7753
  }
6884
7754
 
@@ -6932,6 +7802,41 @@ var requiredOutputStatuses = ["missing", "partial", "complete", "not-applicable"
6932
7802
  function isRequiredOutputStatus(value) {
6933
7803
  return requiredOutputStatuses.includes(value);
6934
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
+ }
6935
7840
  program.name("agent-kit").description("Next.js + Supabase agent, skill, docs, design, and research kit.").version(PACKAGE_VERSION);
6936
7841
  async function runGuidedContextPrompts(cwd) {
6937
7842
  if (!process.stdin.isTTY || !process.stdout.isTTY) return;
@@ -6953,11 +7858,11 @@ async function runGuidedContextPrompts(cwd) {
6953
7858
  if (typeof answer === "string" && answer.trim()) answers[question.key] = answer.trim();
6954
7859
  }
6955
7860
  if (Object.keys(answers).length > 0) {
6956
- const contextPath = join27(cwd, ".agent-kit", "project-context.json");
6957
- if (existsSync23(contextPath)) {
6958
- 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"));
6959
7864
  Object.assign(context2, answers);
6960
- writeFileSync2(contextPath, `${JSON.stringify(context2, null, 2)}
7865
+ writeFileSync3(contextPath, `${JSON.stringify(context2, null, 2)}
6961
7866
  `);
6962
7867
  renderProjectContext(cwd);
6963
7868
  }
@@ -6978,9 +7883,9 @@ async function runSetupServer(options) {
6978
7883
  console.log(`Pixel office (default): ${handle.url}/ | Form fallback: ${handle.url}/wizard`);
6979
7884
  console.log("Pick Quick, Standard, or Complete on first visit. Press Ctrl+C to stop.");
6980
7885
  if (options.open) void openBrowser(`${handle.url}/`);
6981
- await new Promise((resolve3) => {
7886
+ await new Promise((resolve4) => {
6982
7887
  const shutdown = () => {
6983
- void handle.close().finally(resolve3);
7888
+ void handle.close().finally(resolve4);
6984
7889
  };
6985
7890
  process.once("SIGINT", shutdown);
6986
7891
  process.once("SIGTERM", shutdown);
@@ -7045,8 +7950,20 @@ program.command("init").description("Install agent-kit docs and library files in
7045
7950
  await runSetupServer({ port: 9321, host: "127.0.0.1", open: Boolean(options.open) });
7046
7951
  }
7047
7952
  });
7048
- 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) => {
7049
- 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());
7050
7967
  let minimumReadiness;
7051
7968
  if (options.minReadiness) {
7052
7969
  if (!isAuditReadinessLevel(options.minReadiness)) {
@@ -7056,7 +7973,9 @@ program.command("audit").description("Audit an existing project for agent-kit co
7056
7973
  }
7057
7974
  minimumReadiness = options.minReadiness;
7058
7975
  }
7059
- if (options.json) {
7976
+ if (format === "sarif") {
7977
+ printJson(auditReportToSarif(reportV2));
7978
+ } else if (format === "json") {
7060
7979
  printJson(report2);
7061
7980
  } else {
7062
7981
  const readinessStyle = report2.summary.fail > 0 ? style.fail : report2.summary.warn > 0 ? style.warn : style.pass;
@@ -7408,6 +8327,122 @@ correction.command("propose-upstream <id>").description("Create an upstream prop
7408
8327
  if (options.json) printJson(result);
7409
8328
  else line(`Created upstream proposal from correction ${id}.`);
7410
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
+ });
7411
8446
  var studio = program.command("studio").description("Export and serve local Agent Studio views.");
7412
8447
  async function runStudioServer(options) {
7413
8448
  const handle = await startStudioServer({
@@ -7421,9 +8456,9 @@ async function runStudioServer(options) {
7421
8456
  console.log(`Agent Kit v${PACKAGE_VERSION} \u2014 live studio at ${handle.url}/`);
7422
8457
  console.log("SSE: GET /api/events/stream | Press Ctrl+C to stop.");
7423
8458
  if (options.open) void openBrowser(`${handle.url}/`);
7424
- await new Promise((resolve3) => {
8459
+ await new Promise((resolve4) => {
7425
8460
  const shutdown = () => {
7426
- void handle.close().finally(resolve3);
8461
+ void handle.close().finally(resolve4);
7427
8462
  };
7428
8463
  process.once("SIGINT", shutdown);
7429
8464
  process.once("SIGTERM", shutdown);