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