@appsforgood/next-supabase-kit 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BEST_PRACTICE_EVIDENCE.md +1 -1
- package/CHANGELOG.md +9 -0
- package/DOGFOOD.md +2 -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/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 +1723 -688
- package/dist/index.js.map +1 -1
- package/dist/studio/office/assets/office.css +138 -7
- package/dist/studio/office/assets/office.js +200 -2
- 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 +25 -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
|
}
|
|
@@ -363,7 +470,7 @@ function unique(values) {
|
|
|
363
470
|
return [...new Set(values.filter(Boolean))].sort();
|
|
364
471
|
}
|
|
365
472
|
function readJsonBody(request) {
|
|
366
|
-
return new Promise((
|
|
473
|
+
return new Promise((resolve4, reject) => {
|
|
367
474
|
const chunks = [];
|
|
368
475
|
let bodyTooLarge = false;
|
|
369
476
|
request.on("data", (chunk) => {
|
|
@@ -378,11 +485,11 @@ function readJsonBody(request) {
|
|
|
378
485
|
if (bodyTooLarge) return;
|
|
379
486
|
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
380
487
|
if (!raw) {
|
|
381
|
-
|
|
488
|
+
resolve4({});
|
|
382
489
|
return;
|
|
383
490
|
}
|
|
384
491
|
try {
|
|
385
|
-
|
|
492
|
+
resolve4(JSON.parse(raw));
|
|
386
493
|
} catch {
|
|
387
494
|
reject(new Error("Request body must be valid JSON."));
|
|
388
495
|
}
|
|
@@ -392,8 +499,8 @@ function readJsonBody(request) {
|
|
|
392
499
|
}
|
|
393
500
|
|
|
394
501
|
// src/install/audit.ts
|
|
395
|
-
import { existsSync as
|
|
396
|
-
import { join as
|
|
502
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, statSync as statSync3 } from "fs";
|
|
503
|
+
import { join as join13 } from "path";
|
|
397
504
|
|
|
398
505
|
// src/config/contracts.ts
|
|
399
506
|
import { z } from "zod";
|
|
@@ -608,6 +715,8 @@ var StudioSessionContract = z.object({
|
|
|
608
715
|
renderedAt: z.string().datetime().optional()
|
|
609
716
|
}).strict();
|
|
610
717
|
var SessionEventContract = z.object({
|
|
718
|
+
eventId: z.string().uuid().optional(),
|
|
719
|
+
sequence: z.number().int().positive().optional(),
|
|
611
720
|
type: z.enum([
|
|
612
721
|
"session_started",
|
|
613
722
|
"project_context_loaded",
|
|
@@ -1169,8 +1278,8 @@ function onboardingStateExists(cwd) {
|
|
|
1169
1278
|
}
|
|
1170
1279
|
|
|
1171
1280
|
// src/install/install.ts
|
|
1172
|
-
import { existsSync as existsSync10, readFileSync as
|
|
1173
|
-
import { join as
|
|
1281
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
1282
|
+
import { join as join11 } from "path";
|
|
1174
1283
|
|
|
1175
1284
|
// src/install/ide-activate.ts
|
|
1176
1285
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
@@ -1342,10 +1451,11 @@ function writeGeneratedAgentFile(cwd, relativePath, content, force, result) {
|
|
|
1342
1451
|
result.unchanged.push(relativePath);
|
|
1343
1452
|
return;
|
|
1344
1453
|
}
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1454
|
+
const proposal = writeConflictProposal(cwd, relativePath, content, {
|
|
1455
|
+
currentContent: existing,
|
|
1456
|
+
reason: "Generated agent content changed while the local target is customized."
|
|
1457
|
+
});
|
|
1458
|
+
result.conflicts.push(`${relativePath} -> ${proposal.conflictPath}`);
|
|
1349
1459
|
return;
|
|
1350
1460
|
}
|
|
1351
1461
|
ensureDir(join8(cwd, relativePath.split("/").slice(0, -1).join("/")));
|
|
@@ -1613,17 +1723,57 @@ function ideSurfaceToActivateTarget(ideSurface) {
|
|
|
1613
1723
|
return null;
|
|
1614
1724
|
}
|
|
1615
1725
|
|
|
1726
|
+
// src/install/managed-assets.ts
|
|
1727
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
1728
|
+
import { join as join10 } from "path";
|
|
1729
|
+
function listManagedAssets(packageRoot, stack) {
|
|
1730
|
+
const assets = [];
|
|
1731
|
+
const templateRoot = join10(packageRoot, "templates", stack);
|
|
1732
|
+
for (const target of ROOT_DOCS) {
|
|
1733
|
+
assets.push({ target, sourcePath: join10(templateRoot, target), category: "root-doc" });
|
|
1734
|
+
}
|
|
1735
|
+
for (const adapter2 of CURSOR_ADAPTER_FILES) {
|
|
1736
|
+
assets.push({ target: adapter2.target, sourcePath: join10(packageRoot, adapter2.source), category: "adapter" });
|
|
1737
|
+
}
|
|
1738
|
+
assets.push(
|
|
1739
|
+
{ target: DEFAULT_AGENT_ROSTER_TARGET, sourcePath: join10(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE), category: "roster" },
|
|
1740
|
+
{ target: DEFAULT_MODEL_ROUTING_TARGET, sourcePath: join10(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE), category: "model-routing" },
|
|
1741
|
+
{ target: DEFAULT_ORCHESTRATOR_TARGET, sourcePath: join10(packageRoot, DEFAULT_ORCHESTRATOR_SOURCE), category: "orchestrator" },
|
|
1742
|
+
{ target: DEFAULT_RUNTIME_IGNORE_TARGET, sourcePath: join10(packageRoot, DEFAULT_RUNTIME_IGNORE_SOURCE), category: "orchestrator" }
|
|
1743
|
+
);
|
|
1744
|
+
for (const template of CI_TEMPLATE_FILES) {
|
|
1745
|
+
assets.push({ target: template.target, sourcePath: join10(packageRoot, template.source), category: "ci" });
|
|
1746
|
+
}
|
|
1747
|
+
for (const folder of LIBRARY_FOLDERS) {
|
|
1748
|
+
for (const relativePath of listFilesRecursive(join10(packageRoot, folder))) {
|
|
1749
|
+
assets.push({
|
|
1750
|
+
target: `.agent-kit/${folder}/${relativePath}`.replace(/\\/g, "/"),
|
|
1751
|
+
sourcePath: join10(packageRoot, folder, relativePath),
|
|
1752
|
+
category: "library",
|
|
1753
|
+
libraryFolder: folder
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1758
|
+
for (const asset of assets) byTarget.set(asset.target, asset);
|
|
1759
|
+
return [...byTarget.values()].sort((left, right) => left.target.localeCompare(right.target));
|
|
1760
|
+
}
|
|
1761
|
+
function hashManagedAssets(assets) {
|
|
1762
|
+
return Object.fromEntries(assets.map((asset) => [asset.target, sha256(readFileSync8(asset.sourcePath, "utf8"))]));
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1616
1765
|
// src/install/install.ts
|
|
1617
1766
|
function initProject(options) {
|
|
1618
1767
|
const cwd = options.cwd;
|
|
1619
1768
|
const stack = options.stack ?? DEFAULT_CONFIG.stack;
|
|
1620
1769
|
const packageRoot = findPackageRoot();
|
|
1621
|
-
const templateRoot =
|
|
1770
|
+
const templateRoot = join11(packageRoot, "templates", stack);
|
|
1771
|
+
const managedAssets = listManagedAssets(packageRoot, stack);
|
|
1622
1772
|
if (!existsSync10(templateRoot)) {
|
|
1623
1773
|
throw new Error(`Unsupported stack profile: ${stack}`);
|
|
1624
1774
|
}
|
|
1625
|
-
ensureDir(
|
|
1626
|
-
ensureDir(
|
|
1775
|
+
ensureDir(join11(cwd, ".agent-kit"));
|
|
1776
|
+
ensureDir(join11(cwd, ".agent-kit", "conflicts"));
|
|
1627
1777
|
const result = {
|
|
1628
1778
|
copied: [],
|
|
1629
1779
|
unchanged: [],
|
|
@@ -1633,11 +1783,11 @@ function initProject(options) {
|
|
|
1633
1783
|
};
|
|
1634
1784
|
const templateHashes = {};
|
|
1635
1785
|
for (const doc of ROOT_DOCS) {
|
|
1636
|
-
const templatePath =
|
|
1637
|
-
templateHashes[doc] = sha256(
|
|
1786
|
+
const templatePath = join11(templateRoot, doc);
|
|
1787
|
+
templateHashes[doc] = sha256(readFileSync9(templatePath, "utf8"));
|
|
1638
1788
|
const copyResult = copyTextWithConflict(templatePath, cwd, doc, {
|
|
1639
1789
|
force: Boolean(options.force),
|
|
1640
|
-
conflictRoot:
|
|
1790
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1641
1791
|
});
|
|
1642
1792
|
if (copyResult.action === "created") result.copied.push(copyResult.target);
|
|
1643
1793
|
if (copyResult.action === "unchanged") result.unchanged.push(copyResult.target);
|
|
@@ -1646,13 +1796,18 @@ function initProject(options) {
|
|
|
1646
1796
|
result.conflicts.push(`${copyResult.target} -> ${copyResult.conflictPath}`);
|
|
1647
1797
|
}
|
|
1648
1798
|
}
|
|
1649
|
-
for (const
|
|
1650
|
-
|
|
1799
|
+
for (const asset of managedAssets.filter((item) => item.category === "library")) {
|
|
1800
|
+
const libraryCopy = copyTextWithConflict(asset.sourcePath, cwd, asset.target, {
|
|
1801
|
+
force: Boolean(options.force),
|
|
1802
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1803
|
+
});
|
|
1804
|
+
if (libraryCopy.action === "overwritten") result.overwritten.push(libraryCopy.target);
|
|
1805
|
+
if (libraryCopy.action === "conflict") result.conflicts.push(`${libraryCopy.target} -> ${libraryCopy.conflictPath}`);
|
|
1651
1806
|
}
|
|
1652
1807
|
for (const adapter2 of CURSOR_ADAPTER_FILES) {
|
|
1653
|
-
const adapterCopy = copyTextWithConflict(
|
|
1808
|
+
const adapterCopy = copyTextWithConflict(join11(packageRoot, adapter2.source), cwd, adapter2.target, {
|
|
1654
1809
|
force: Boolean(options.force),
|
|
1655
|
-
conflictRoot:
|
|
1810
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1656
1811
|
});
|
|
1657
1812
|
if (adapterCopy.action === "created") result.copied.push(adapterCopy.target);
|
|
1658
1813
|
if (adapterCopy.action === "unchanged") result.unchanged.push(adapterCopy.target);
|
|
@@ -1661,23 +1816,40 @@ function initProject(options) {
|
|
|
1661
1816
|
result.conflicts.push(`${adapterCopy.target} -> ${adapterCopy.conflictPath}`);
|
|
1662
1817
|
}
|
|
1663
1818
|
}
|
|
1664
|
-
const rosterCopy = copyTextWithConflict(
|
|
1819
|
+
const rosterCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE), cwd, DEFAULT_AGENT_ROSTER_TARGET, {
|
|
1665
1820
|
force: Boolean(options.force),
|
|
1666
|
-
conflictRoot:
|
|
1821
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1667
1822
|
});
|
|
1668
1823
|
if (rosterCopy.action === "created") result.copied.push(rosterCopy.target);
|
|
1669
1824
|
if (rosterCopy.action === "unchanged") result.unchanged.push(rosterCopy.target);
|
|
1670
1825
|
if (rosterCopy.action === "overwritten") result.overwritten.push(rosterCopy.target);
|
|
1671
1826
|
if (rosterCopy.action === "conflict") result.conflicts.push(`${rosterCopy.target} -> ${rosterCopy.conflictPath}`);
|
|
1672
|
-
const modelRoutingCopy = copyTextWithConflict(
|
|
1827
|
+
const modelRoutingCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE), cwd, DEFAULT_MODEL_ROUTING_TARGET, {
|
|
1673
1828
|
force: Boolean(options.force),
|
|
1674
|
-
conflictRoot:
|
|
1829
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1675
1830
|
});
|
|
1676
1831
|
if (modelRoutingCopy.action === "created") result.copied.push(modelRoutingCopy.target);
|
|
1677
1832
|
if (modelRoutingCopy.action === "unchanged") result.unchanged.push(modelRoutingCopy.target);
|
|
1678
1833
|
if (modelRoutingCopy.action === "overwritten") result.overwritten.push(modelRoutingCopy.target);
|
|
1679
1834
|
if (modelRoutingCopy.action === "conflict") result.conflicts.push(`${modelRoutingCopy.target} -> ${modelRoutingCopy.conflictPath}`);
|
|
1835
|
+
const orchestratorCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_ORCHESTRATOR_SOURCE), cwd, DEFAULT_ORCHESTRATOR_TARGET, {
|
|
1836
|
+
force: Boolean(options.force),
|
|
1837
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1838
|
+
});
|
|
1839
|
+
if (orchestratorCopy.action === "created") result.copied.push(orchestratorCopy.target);
|
|
1840
|
+
if (orchestratorCopy.action === "unchanged") result.unchanged.push(orchestratorCopy.target);
|
|
1841
|
+
if (orchestratorCopy.action === "overwritten") result.overwritten.push(orchestratorCopy.target);
|
|
1842
|
+
if (orchestratorCopy.action === "conflict") result.conflicts.push(`${orchestratorCopy.target} -> ${orchestratorCopy.conflictPath}`);
|
|
1843
|
+
const runtimeIgnoreCopy = copyTextWithConflict(join11(packageRoot, DEFAULT_RUNTIME_IGNORE_SOURCE), cwd, DEFAULT_RUNTIME_IGNORE_TARGET, {
|
|
1844
|
+
force: Boolean(options.force),
|
|
1845
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1846
|
+
});
|
|
1847
|
+
if (runtimeIgnoreCopy.action === "created") result.copied.push(runtimeIgnoreCopy.target);
|
|
1848
|
+
if (runtimeIgnoreCopy.action === "unchanged") result.unchanged.push(runtimeIgnoreCopy.target);
|
|
1849
|
+
if (runtimeIgnoreCopy.action === "overwritten") result.overwritten.push(runtimeIgnoreCopy.target);
|
|
1850
|
+
if (runtimeIgnoreCopy.action === "conflict") result.conflicts.push(`${runtimeIgnoreCopy.target} -> ${runtimeIgnoreCopy.conflictPath}`);
|
|
1680
1851
|
const manifest = {
|
|
1852
|
+
schemaVersion: 2,
|
|
1681
1853
|
packageName: PACKAGE_NAME,
|
|
1682
1854
|
packageVersion: PACKAGE_VERSION,
|
|
1683
1855
|
stack,
|
|
@@ -1686,19 +1858,20 @@ function initProject(options) {
|
|
|
1686
1858
|
libraryFolders: [...LIBRARY_FOLDERS],
|
|
1687
1859
|
agentRoster: DEFAULT_AGENT_ROSTER_TARGET,
|
|
1688
1860
|
modelRouting: DEFAULT_MODEL_ROUTING_TARGET,
|
|
1689
|
-
templateHashes
|
|
1861
|
+
templateHashes,
|
|
1862
|
+
assetHashes: hashManagedAssets(managedAssets)
|
|
1690
1863
|
};
|
|
1691
|
-
writeText(
|
|
1864
|
+
writeText(join11(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
1692
1865
|
`);
|
|
1693
|
-
writeText(
|
|
1866
|
+
writeText(join11(cwd, ".agent-kit", "config.json"), `${JSON.stringify(DEFAULT_CONFIG, null, 2)}
|
|
1694
1867
|
`);
|
|
1695
|
-
const overridesPath =
|
|
1868
|
+
const overridesPath = join11(cwd, ".agent-kit", "overrides.json");
|
|
1696
1869
|
if (!existsSync10(overridesPath)) writeText(overridesPath, `${JSON.stringify({ templates: {} }, null, 2)}
|
|
1697
1870
|
`);
|
|
1698
1871
|
for (const template of CI_TEMPLATE_FILES) {
|
|
1699
|
-
const ciCopy = copyTextWithConflict(
|
|
1872
|
+
const ciCopy = copyTextWithConflict(join11(packageRoot, template.source), cwd, template.target, {
|
|
1700
1873
|
force: Boolean(options.force),
|
|
1701
|
-
conflictRoot:
|
|
1874
|
+
conflictRoot: join11(cwd, ".agent-kit", "conflicts")
|
|
1702
1875
|
});
|
|
1703
1876
|
if (ciCopy.action === "created") result.copied.push(ciCopy.target);
|
|
1704
1877
|
if (ciCopy.action === "unchanged") result.unchanged.push(ciCopy.target);
|
|
@@ -1722,10 +1895,278 @@ function initProject(options) {
|
|
|
1722
1895
|
return result;
|
|
1723
1896
|
}
|
|
1724
1897
|
function readManifest(cwd) {
|
|
1725
|
-
const manifestPath =
|
|
1898
|
+
const manifestPath = join11(cwd, ".agent-kit", "manifest.json");
|
|
1726
1899
|
if (!existsSync10(manifestPath)) return null;
|
|
1727
|
-
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;
|
|
1728
2049
|
}
|
|
2050
|
+
function evaluateTests(context2) {
|
|
2051
|
+
const packageJson = readPackageJson2(context2.cwd);
|
|
2052
|
+
if (!packageJson) {
|
|
2053
|
+
return {
|
|
2054
|
+
level: "warn",
|
|
2055
|
+
area: "project-reality",
|
|
2056
|
+
message: "No package.json found to verify test scripts.",
|
|
2057
|
+
remediation: "Add package.json with executable test, lint, and build scripts appropriate to the stack.",
|
|
2058
|
+
confidence: "high",
|
|
2059
|
+
evidence: []
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
const scripts = packageJson.scripts ?? {};
|
|
2063
|
+
const entry = ["test", "test:unit", "test:ci"].find((name) => scripts[name]);
|
|
2064
|
+
if (!entry) {
|
|
2065
|
+
return {
|
|
2066
|
+
level: "warn",
|
|
2067
|
+
area: "project-reality",
|
|
2068
|
+
message: "package.json has no test script (test, test:unit, or test:ci).",
|
|
2069
|
+
remediation: "Add an executable test command and document it in TESTING.md.",
|
|
2070
|
+
confidence: "high",
|
|
2071
|
+
evidence: [{ kind: "file", path: "package.json", summary: "No supported test script key was found.", observedAt: context2.observedAt }]
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
const script = scripts[entry] ?? "";
|
|
2075
|
+
const files = listFilesRecursive(context2.cwd);
|
|
2076
|
+
const hasTests = files.some((file) => /(^|\/)(?:tests?|__tests__)(\/|$)|\.(?:test|spec)\.[cm]?[jt]sx?$/i.test(file));
|
|
2077
|
+
if (!testScriptLooksExecutable(script) || !hasTests) {
|
|
2078
|
+
return {
|
|
2079
|
+
level: "warn",
|
|
2080
|
+
area: "project-reality",
|
|
2081
|
+
message: `package.json script ${entry} does not provide credible executable test evidence.`,
|
|
2082
|
+
remediation: "Use a recognized test runner and keep at least one discoverable test file in the repository.",
|
|
2083
|
+
confidence: "high",
|
|
2084
|
+
evidence: [{ kind: "configuration", path: "package.json", summary: `${entry}: ${script}`, observedAt: context2.observedAt }]
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
return {
|
|
2088
|
+
level: "pass",
|
|
2089
|
+
area: "project-reality",
|
|
2090
|
+
message: `package.json defines executable test script ${entry} and discoverable test files exist.`,
|
|
2091
|
+
confidence: "high",
|
|
2092
|
+
evidence: [{ kind: "configuration", path: "package.json", summary: `${entry}: ${script}`, observedAt: context2.observedAt }]
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
function evaluateSecrets(context2) {
|
|
2096
|
+
const inventory = relativeFilesFromGit(context2.cwd);
|
|
2097
|
+
const candidates = inventory.files.filter((file) => {
|
|
2098
|
+
const normalized = file.replace(/\\/g, "/");
|
|
2099
|
+
if (normalized.includes("node_modules/") || normalized.includes(".agent-kit/")) return false;
|
|
2100
|
+
const path = join12(context2.cwd, file);
|
|
2101
|
+
try {
|
|
2102
|
+
return statSync2(path).isFile() && statSync2(path).size <= 1e6;
|
|
2103
|
+
} catch {
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
const hits = candidates.flatMap((file) => {
|
|
2108
|
+
try {
|
|
2109
|
+
const content = readFileSync10(join12(context2.cwd, file), "utf8");
|
|
2110
|
+
if (content.includes("\0")) return [];
|
|
2111
|
+
return containsLikelySecretForAudit(file, content) ? [file.replace(/\\/g, "/")] : [];
|
|
2112
|
+
} catch {
|
|
2113
|
+
return [];
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
if (hits.length > 0) {
|
|
2117
|
+
return {
|
|
2118
|
+
level: "fail",
|
|
2119
|
+
area: "project-reality",
|
|
2120
|
+
message: `Possible ${inventory.tracked ? "committed" : "workspace"} secret patterns detected in: ${hits.slice(0, 5).join(", ")}.`,
|
|
2121
|
+
remediation: "Remove secrets, rotate exposed credentials, and store only credential references in project files.",
|
|
2122
|
+
confidence: inventory.tracked ? "high" : "medium",
|
|
2123
|
+
evidence: hits.slice(0, 20).map((path) => ({ kind: "file", path, summary: "Secret-like pattern detected; value omitted.", observedAt: context2.observedAt }))
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
if (candidates.length === 0) return null;
|
|
2127
|
+
return {
|
|
2128
|
+
level: "pass",
|
|
2129
|
+
area: "project-reality",
|
|
2130
|
+
message: `No obvious secret patterns detected in ${inventory.tracked ? "Git-tracked" : "workspace"} files.`,
|
|
2131
|
+
confidence: inventory.tracked ? "high" : "medium",
|
|
2132
|
+
evidence: [{ kind: "command", summary: inventory.tracked ? "git ls-files --cached" : "Recursive workspace fallback", observedAt: context2.observedAt }]
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
function evaluateContext(context2) {
|
|
2136
|
+
if (context2.packageRepository) {
|
|
2137
|
+
return {
|
|
2138
|
+
level: "pass",
|
|
2139
|
+
area: "project-reality",
|
|
2140
|
+
message: "Package source repository mode does not require installed-project context files.",
|
|
2141
|
+
confidence: "high",
|
|
2142
|
+
evidence: []
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
if (!existsSync11(join12(context2.cwd, CONTEXT_JSON))) {
|
|
2146
|
+
return {
|
|
2147
|
+
level: "warn",
|
|
2148
|
+
area: "project-reality",
|
|
2149
|
+
message: ".agent-kit/project-context.json is missing.",
|
|
2150
|
+
remediation: "Run agent-kit init or agent-kit context init to create project context.",
|
|
2151
|
+
confidence: "high",
|
|
2152
|
+
evidence: []
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
return {
|
|
2156
|
+
level: "pass",
|
|
2157
|
+
area: "project-reality",
|
|
2158
|
+
message: ".agent-kit/project-context.json exists.",
|
|
2159
|
+
confidence: "high",
|
|
2160
|
+
evidence: [{ kind: "file", path: CONTEXT_JSON, summary: "Project context file exists.", observedAt: context2.observedAt }]
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
var projectRealityRules = new AuditRuleRegistry().register({
|
|
2164
|
+
id: "project-reality.supabase.rls-per-table",
|
|
2165
|
+
version: "1.0.0",
|
|
2166
|
+
area: "project-reality",
|
|
2167
|
+
helpUri: "https://supabase.com/docs/guides/database/postgres/row-level-security",
|
|
2168
|
+
evaluate: evaluateRls
|
|
2169
|
+
}).register({ id: "project-reality.tests.executable-script", version: "1.0.0", area: "project-reality", evaluate: evaluateTests }).register({ id: "project-reality.secrets.git-tracked", version: "1.0.0", area: "project-reality", evaluate: evaluateSecrets }).register({ id: "project-reality.context.present", version: "1.0.0", area: "project-reality", evaluate: evaluateContext });
|
|
1729
2170
|
|
|
1730
2171
|
// src/install/audit.ts
|
|
1731
2172
|
var REQUIRED_AGENT_IDS = [
|
|
@@ -1770,13 +2211,17 @@ var REQUIRED_SCHEMA_FILES = [
|
|
|
1770
2211
|
"agent-roster.schema.json",
|
|
1771
2212
|
"council-session.schema.json",
|
|
1772
2213
|
"audit-report.schema.json",
|
|
2214
|
+
"audit-report-v2.schema.json",
|
|
1773
2215
|
"model-routing.schema.json",
|
|
1774
2216
|
"project-context.schema.json",
|
|
1775
2217
|
"correction-rules.schema.json",
|
|
1776
2218
|
"session-event.schema.json",
|
|
1777
2219
|
"studio-session.schema.json",
|
|
1778
2220
|
"onboarding-state.schema.json",
|
|
1779
|
-
"agentic-level.schema.json"
|
|
2221
|
+
"agentic-level.schema.json",
|
|
2222
|
+
"orchestrator.schema.json",
|
|
2223
|
+
"runtime-run.schema.json",
|
|
2224
|
+
"runtime-event.schema.json"
|
|
1780
2225
|
];
|
|
1781
2226
|
var COUNCIL_SESSION_DIR = ".agent-kit/council-sessions";
|
|
1782
2227
|
var READINESS_ORDER = ["needs-setup", "baseline-setup", "needs-improvement", "best-practice-candidate"];
|
|
@@ -1795,24 +2240,24 @@ function includesAll(text, values) {
|
|
|
1795
2240
|
return values.every((value) => lower.includes(value.toLowerCase()));
|
|
1796
2241
|
}
|
|
1797
2242
|
function readDoc(cwd, file) {
|
|
1798
|
-
const path =
|
|
1799
|
-
return
|
|
2243
|
+
const path = join13(cwd, file);
|
|
2244
|
+
return existsSync12(path) ? readFileSync11(path, "utf8") : "";
|
|
1800
2245
|
}
|
|
1801
2246
|
function isPackageRepository(cwd) {
|
|
1802
|
-
const packagePath =
|
|
1803
|
-
if (!
|
|
2247
|
+
const packagePath = join13(cwd, "package.json");
|
|
2248
|
+
if (!existsSync12(packagePath)) return false;
|
|
1804
2249
|
try {
|
|
1805
|
-
const packageJson = JSON.parse(
|
|
1806
|
-
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"));
|
|
1807
2252
|
} catch {
|
|
1808
2253
|
return false;
|
|
1809
2254
|
}
|
|
1810
2255
|
}
|
|
1811
2256
|
function readOverrides(cwd) {
|
|
1812
|
-
const path =
|
|
1813
|
-
if (!
|
|
2257
|
+
const path = join13(cwd, ".agent-kit", "overrides.json");
|
|
2258
|
+
if (!existsSync12(path)) return {};
|
|
1814
2259
|
try {
|
|
1815
|
-
const parsed = JSON.parse(
|
|
2260
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
1816
2261
|
const templates = parsed.templates ?? {};
|
|
1817
2262
|
return Object.fromEntries(
|
|
1818
2263
|
Object.entries(templates).map(([file, override]) => [
|
|
@@ -1825,8 +2270,8 @@ function readOverrides(cwd) {
|
|
|
1825
2270
|
}
|
|
1826
2271
|
}
|
|
1827
2272
|
function readTemplate(stack, file) {
|
|
1828
|
-
const path =
|
|
1829
|
-
return
|
|
2273
|
+
const path = join13(findPackageRoot(), "templates", stack, file);
|
|
2274
|
+
return existsSync12(path) ? readFileSync11(path, "utf8") : null;
|
|
1830
2275
|
}
|
|
1831
2276
|
function asStringArray(value) {
|
|
1832
2277
|
if (!Array.isArray(value)) return [];
|
|
@@ -1836,8 +2281,8 @@ function isRecord(value) {
|
|
|
1836
2281
|
return typeof value === "object" && value !== null;
|
|
1837
2282
|
}
|
|
1838
2283
|
function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGENT_ROSTER_TARGET) {
|
|
1839
|
-
const rosterPath =
|
|
1840
|
-
if (!
|
|
2284
|
+
const rosterPath = join13(cwd, rosterRelativePath);
|
|
2285
|
+
if (!existsSync12(rosterPath)) {
|
|
1841
2286
|
findings.push({
|
|
1842
2287
|
level: "fail",
|
|
1843
2288
|
area: "agents",
|
|
@@ -1848,7 +2293,7 @@ function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGEN
|
|
|
1848
2293
|
}
|
|
1849
2294
|
let roster;
|
|
1850
2295
|
try {
|
|
1851
|
-
const parsed = JSON.parse(
|
|
2296
|
+
const parsed = JSON.parse(readFileSync11(rosterPath, "utf8"));
|
|
1852
2297
|
if (!isRecord(parsed)) throw new Error("Roster must be a JSON object.");
|
|
1853
2298
|
const contractResult = AgentRosterContract.safeParse(parsed);
|
|
1854
2299
|
if (!contractResult.success) {
|
|
@@ -2035,15 +2480,15 @@ function addAgentRosterFindings(cwd, findings, rosterRelativePath = DEFAULT_AGEN
|
|
|
2035
2480
|
}
|
|
2036
2481
|
}
|
|
2037
2482
|
function addCouncilSessionRecordFindings(cwd, findings) {
|
|
2038
|
-
const sessionsRoot =
|
|
2039
|
-
if (!
|
|
2483
|
+
const sessionsRoot = join13(cwd, COUNCIL_SESSION_DIR);
|
|
2484
|
+
if (!existsSync12(sessionsRoot)) return;
|
|
2040
2485
|
const sessionFiles = listFilesRecursive(sessionsRoot).filter((file) => file.endsWith(".json") && !/[\\/]/.test(file));
|
|
2041
2486
|
if (sessionFiles.length === 0) return;
|
|
2042
2487
|
let invalidCount = 0;
|
|
2043
2488
|
for (const sessionFile of sessionFiles) {
|
|
2044
2489
|
const displayPath = `${COUNCIL_SESSION_DIR}/${sessionFile}`;
|
|
2045
2490
|
try {
|
|
2046
|
-
const parsed = JSON.parse(
|
|
2491
|
+
const parsed = JSON.parse(readFileSync11(join13(sessionsRoot, sessionFile), "utf8"));
|
|
2047
2492
|
const contractResult = CouncilSessionContract.safeParse(parsed);
|
|
2048
2493
|
if (!contractResult.success) {
|
|
2049
2494
|
invalidCount += 1;
|
|
@@ -2074,8 +2519,8 @@ function addCouncilSessionRecordFindings(cwd, findings) {
|
|
|
2074
2519
|
}
|
|
2075
2520
|
function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/schemas") {
|
|
2076
2521
|
for (const schemaFile of REQUIRED_SCHEMA_FILES) {
|
|
2077
|
-
const schemaPath =
|
|
2078
|
-
if (!
|
|
2522
|
+
const schemaPath = join13(cwd, schemaRootRelativePath, schemaFile);
|
|
2523
|
+
if (!existsSync12(schemaPath)) {
|
|
2079
2524
|
findings.push({
|
|
2080
2525
|
level: "warn",
|
|
2081
2526
|
area: "agents",
|
|
@@ -2085,7 +2530,7 @@ function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/s
|
|
|
2085
2530
|
continue;
|
|
2086
2531
|
}
|
|
2087
2532
|
try {
|
|
2088
|
-
const parsed = JSON.parse(
|
|
2533
|
+
const parsed = JSON.parse(readFileSync11(schemaPath, "utf8"));
|
|
2089
2534
|
if (!isRecord(parsed) || typeof parsed.$schema !== "string" || !isRecord(parsed.properties)) {
|
|
2090
2535
|
throw new Error("Schema file is missing JSON Schema metadata.");
|
|
2091
2536
|
}
|
|
@@ -2104,9 +2549,54 @@ function addSchemaFindings(cwd, findings, schemaRootRelativePath = ".agent-kit/s
|
|
|
2104
2549
|
}
|
|
2105
2550
|
}
|
|
2106
2551
|
}
|
|
2552
|
+
function addOrchestratorFindings(cwd, findings, relativePath = DEFAULT_ORCHESTRATOR_TARGET) {
|
|
2553
|
+
const path = join13(cwd, relativePath);
|
|
2554
|
+
if (!existsSync12(path)) {
|
|
2555
|
+
findings.push({
|
|
2556
|
+
level: "warn",
|
|
2557
|
+
area: "orchestrator",
|
|
2558
|
+
message: `${relativePath} is missing.`,
|
|
2559
|
+
remediation: "Run agent-kit update to install the disabled-by-default optional runtime policy."
|
|
2560
|
+
});
|
|
2561
|
+
return;
|
|
2562
|
+
}
|
|
2563
|
+
const text = readFileSync11(path, "utf8");
|
|
2564
|
+
if (containsLikelySecret(text)) {
|
|
2565
|
+
findings.push({
|
|
2566
|
+
level: "fail",
|
|
2567
|
+
area: "orchestrator",
|
|
2568
|
+
message: `${relativePath} appears to contain a resolved credential.`,
|
|
2569
|
+
remediation: "Replace credential values with env:NAME or keychain:account references and rotate exposed secrets."
|
|
2570
|
+
});
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
try {
|
|
2574
|
+
const config = JSON.parse(text);
|
|
2575
|
+
if (config.schemaVersion !== 1 || typeof config.enabled !== "boolean") throw new Error("schemaVersion must be 1 and enabled must be boolean");
|
|
2576
|
+
if (!config.providers || typeof config.providers !== "object" || Array.isArray(config.providers)) throw new Error("providers must be an object");
|
|
2577
|
+
if (!config.modelAliases || typeof config.modelAliases !== "object" || Array.isArray(config.modelAliases))
|
|
2578
|
+
throw new Error("modelAliases must be an object");
|
|
2579
|
+
if (config.enabled) {
|
|
2580
|
+
if (typeof config.defaultAlias !== "string" || !config.defaultAlias) throw new Error("enabled runtime requires defaultAlias");
|
|
2581
|
+
if (!Object.hasOwn(config.modelAliases, config.defaultAlias)) throw new Error("enabled runtime defaultAlias is not configured");
|
|
2582
|
+
}
|
|
2583
|
+
findings.push({
|
|
2584
|
+
level: "pass",
|
|
2585
|
+
area: "orchestrator",
|
|
2586
|
+
message: `${relativePath} is valid and ${config.enabled ? "enabled" : "disabled by default"}.`
|
|
2587
|
+
});
|
|
2588
|
+
} catch (error) {
|
|
2589
|
+
findings.push({
|
|
2590
|
+
level: "fail",
|
|
2591
|
+
area: "orchestrator",
|
|
2592
|
+
message: `${relativePath} is invalid: ${error instanceof Error ? error.message : String(error)}.`,
|
|
2593
|
+
remediation: "Restore it from the current template and run agent-kit orchestrate validate before enabling execution."
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2107
2597
|
function addAgentStudioFindings(cwd, findings) {
|
|
2108
|
-
const contextPath =
|
|
2109
|
-
if (!
|
|
2598
|
+
const contextPath = join13(cwd, CONTEXT_JSON);
|
|
2599
|
+
if (!existsSync12(contextPath)) {
|
|
2110
2600
|
findings.push({
|
|
2111
2601
|
level: "warn",
|
|
2112
2602
|
area: "studio",
|
|
@@ -2115,7 +2605,7 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2115
2605
|
});
|
|
2116
2606
|
} else {
|
|
2117
2607
|
try {
|
|
2118
|
-
const parsed = JSON.parse(
|
|
2608
|
+
const parsed = JSON.parse(readFileSync11(contextPath, "utf8"));
|
|
2119
2609
|
const result = ProjectContextContract.safeParse(parsed);
|
|
2120
2610
|
if (!result.success) {
|
|
2121
2611
|
findings.push({
|
|
@@ -2179,8 +2669,8 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2179
2669
|
});
|
|
2180
2670
|
}
|
|
2181
2671
|
}
|
|
2182
|
-
const contextMdPath =
|
|
2183
|
-
if (
|
|
2672
|
+
const contextMdPath = join13(cwd, CONTEXT_MD);
|
|
2673
|
+
if (existsSync12(contextPath) && !existsSync12(contextMdPath)) {
|
|
2184
2674
|
findings.push({
|
|
2185
2675
|
level: "warn",
|
|
2186
2676
|
area: "studio",
|
|
@@ -2189,10 +2679,10 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2189
2679
|
});
|
|
2190
2680
|
}
|
|
2191
2681
|
for (const relativePath of [PROJECT_RULES_JSON, AGENT_RULES_JSON]) {
|
|
2192
|
-
const path =
|
|
2193
|
-
if (!
|
|
2682
|
+
const path = join13(cwd, relativePath);
|
|
2683
|
+
if (!existsSync12(path)) continue;
|
|
2194
2684
|
try {
|
|
2195
|
-
const parsed = JSON.parse(
|
|
2685
|
+
const parsed = JSON.parse(readFileSync11(path, "utf8"));
|
|
2196
2686
|
const result = CorrectionRulesContract.safeParse(parsed);
|
|
2197
2687
|
if (!result.success) {
|
|
2198
2688
|
findings.push({
|
|
@@ -2217,9 +2707,9 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2217
2707
|
});
|
|
2218
2708
|
}
|
|
2219
2709
|
}
|
|
2220
|
-
const studioExportPath =
|
|
2221
|
-
if (
|
|
2222
|
-
const exportHtml =
|
|
2710
|
+
const studioExportPath = join13(cwd, STUDIO_EXPORT_HTML);
|
|
2711
|
+
if (existsSync12(studioExportPath)) {
|
|
2712
|
+
const exportHtml = readFileSync11(studioExportPath, "utf8");
|
|
2223
2713
|
if (containsLikelySecret(exportHtml)) {
|
|
2224
2714
|
findings.push({
|
|
2225
2715
|
level: "fail",
|
|
@@ -2242,8 +2732,8 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2242
2732
|
});
|
|
2243
2733
|
}
|
|
2244
2734
|
}
|
|
2245
|
-
const sessionsRoot =
|
|
2246
|
-
if (!
|
|
2735
|
+
const sessionsRoot = join13(cwd, COUNCIL_SESSION_DIR);
|
|
2736
|
+
if (!existsSync12(sessionsRoot)) return;
|
|
2247
2737
|
const files = listFilesRecursive(sessionsRoot);
|
|
2248
2738
|
const studioSessionFiles = files.filter((file) => /[\\/]session\.json$/.test(file));
|
|
2249
2739
|
for (const sessionFile of studioSessionFiles) {
|
|
@@ -2252,10 +2742,10 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2252
2742
|
const sessionDir2 = sessionFile.replace(/[\\/]session\.json$/, "");
|
|
2253
2743
|
const normalizedSessionDir = sessionDir2.replace(/\\/g, "/");
|
|
2254
2744
|
const eventsRelative = `${COUNCIL_SESSION_DIR}/${normalizedSessionDir}/events.jsonl`;
|
|
2255
|
-
const sessionDirPath =
|
|
2745
|
+
const sessionDirPath = join13(sessionsRoot, sessionDir2);
|
|
2256
2746
|
let sessionResult = null;
|
|
2257
2747
|
try {
|
|
2258
|
-
sessionResult = StudioSessionContract.safeParse(JSON.parse(
|
|
2748
|
+
sessionResult = StudioSessionContract.safeParse(JSON.parse(readFileSync11(join13(sessionDirPath, "session.json"), "utf8")));
|
|
2259
2749
|
if (!sessionResult.success) {
|
|
2260
2750
|
findings.push({
|
|
2261
2751
|
level: "fail",
|
|
@@ -2274,8 +2764,8 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2274
2764
|
});
|
|
2275
2765
|
continue;
|
|
2276
2766
|
}
|
|
2277
|
-
const eventsPath2 =
|
|
2278
|
-
if (!
|
|
2767
|
+
const eventsPath2 = join13(sessionDirPath, "events.jsonl");
|
|
2768
|
+
if (!existsSync12(eventsPath2)) {
|
|
2279
2769
|
findings.push({
|
|
2280
2770
|
level: "fail",
|
|
2281
2771
|
area: "studio",
|
|
@@ -2284,7 +2774,7 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2284
2774
|
});
|
|
2285
2775
|
continue;
|
|
2286
2776
|
}
|
|
2287
|
-
const eventText =
|
|
2777
|
+
const eventText = readFileSync11(eventsPath2, "utf8");
|
|
2288
2778
|
if (containsLikelySecret(eventText)) {
|
|
2289
2779
|
findings.push({
|
|
2290
2780
|
level: "fail",
|
|
@@ -2319,7 +2809,7 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2319
2809
|
});
|
|
2320
2810
|
}
|
|
2321
2811
|
}
|
|
2322
|
-
if (!
|
|
2812
|
+
if (!existsSync12(join13(sessionDirPath, "index.md")) || !existsSync12(join13(sessionDirPath, "transcript.md"))) {
|
|
2323
2813
|
findings.push({
|
|
2324
2814
|
level: "warn",
|
|
2325
2815
|
area: "studio",
|
|
@@ -2327,8 +2817,8 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2327
2817
|
remediation: "Run agent-kit session render so humans can inspect the current agent transcript and handoffs."
|
|
2328
2818
|
});
|
|
2329
2819
|
} else {
|
|
2330
|
-
const indexText =
|
|
2331
|
-
const transcriptText =
|
|
2820
|
+
const indexText = readFileSync11(join13(sessionDirPath, "index.md"), "utf8");
|
|
2821
|
+
const transcriptText = readFileSync11(join13(sessionDirPath, "transcript.md"), "utf8");
|
|
2332
2822
|
if (containsLikelySecret(indexText) || containsLikelySecret(transcriptText)) {
|
|
2333
2823
|
findings.push({
|
|
2334
2824
|
level: "fail",
|
|
@@ -2337,7 +2827,7 @@ function addAgentStudioFindings(cwd, findings) {
|
|
|
2337
2827
|
remediation: "Regenerate Markdown after redacting sensitive values from the event log."
|
|
2338
2828
|
});
|
|
2339
2829
|
}
|
|
2340
|
-
if (
|
|
2830
|
+
if (statSync3(eventsPath2).mtimeMs > statSync3(join13(sessionDirPath, "index.md")).mtimeMs) {
|
|
2341
2831
|
findings.push({
|
|
2342
2832
|
level: "warn",
|
|
2343
2833
|
area: "studio",
|
|
@@ -2387,8 +2877,8 @@ function addCouncilDocFindings(cwd, findings) {
|
|
|
2387
2877
|
}
|
|
2388
2878
|
function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".agent-kit/assistant-adapters", docsCwd = cwd) {
|
|
2389
2879
|
const adaptersDoc = readDoc(docsCwd, "ASSISTANT_ADAPTERS.md");
|
|
2390
|
-
const adapterRoot =
|
|
2391
|
-
if (!
|
|
2880
|
+
const adapterRoot = join13(cwd, adapterRootRelativePath);
|
|
2881
|
+
if (!existsSync12(adapterRoot)) {
|
|
2392
2882
|
findings.push({
|
|
2393
2883
|
level: "warn",
|
|
2394
2884
|
area: "agents",
|
|
@@ -2417,7 +2907,7 @@ function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".
|
|
|
2417
2907
|
message: "ASSISTANT_ADAPTERS.md maps the council roster to tool-specific instruction surfaces."
|
|
2418
2908
|
});
|
|
2419
2909
|
}
|
|
2420
|
-
if (assistantAdapterRowIsActive(adaptersDoc, "Cursor") && !
|
|
2910
|
+
if (assistantAdapterRowIsActive(adaptersDoc, "Cursor") && !existsSync12(join13(cwd, ".cursor/agents/planner.md"))) {
|
|
2421
2911
|
findings.push({
|
|
2422
2912
|
level: "warn",
|
|
2423
2913
|
area: "agents",
|
|
@@ -2425,7 +2915,7 @@ function addAssistantAdapterFindings(cwd, findings, adapterRootRelativePath = ".
|
|
|
2425
2915
|
remediation: "Run agent-kit init --activate cursor to generate council subagents from the roster."
|
|
2426
2916
|
});
|
|
2427
2917
|
}
|
|
2428
|
-
if (assistantAdapterRowIsActive(adaptersDoc, "Codex / AGENTS.md-compatible tools") && !
|
|
2918
|
+
if (assistantAdapterRowIsActive(adaptersDoc, "Codex / AGENTS.md-compatible tools") && !existsSync12(join13(cwd, ".codex/agents/planner.toml"))) {
|
|
2429
2919
|
findings.push({
|
|
2430
2920
|
level: "warn",
|
|
2431
2921
|
area: "agents",
|
|
@@ -2473,8 +2963,8 @@ function addModelRoutingFindings(cwd, findings, routingRelativePath = DEFAULT_MO
|
|
|
2473
2963
|
message: "MODEL_ROUTING.md documents agent model profiles and IDE enforcement limits."
|
|
2474
2964
|
});
|
|
2475
2965
|
}
|
|
2476
|
-
const routingPath =
|
|
2477
|
-
if (!
|
|
2966
|
+
const routingPath = join13(cwd, routingRelativePath);
|
|
2967
|
+
if (!existsSync12(routingPath)) {
|
|
2478
2968
|
findings.push({
|
|
2479
2969
|
level: "warn",
|
|
2480
2970
|
area: "models",
|
|
@@ -2485,7 +2975,7 @@ function addModelRoutingFindings(cwd, findings, routingRelativePath = DEFAULT_MO
|
|
|
2485
2975
|
}
|
|
2486
2976
|
let routing;
|
|
2487
2977
|
try {
|
|
2488
|
-
routing = JSON.parse(
|
|
2978
|
+
routing = JSON.parse(readFileSync11(routingPath, "utf8"));
|
|
2489
2979
|
} catch {
|
|
2490
2980
|
findings.push({
|
|
2491
2981
|
level: "warn",
|
|
@@ -2543,11 +3033,11 @@ function addTemplateHashFindings(cwd, findings) {
|
|
|
2543
3033
|
if (!manifest) return;
|
|
2544
3034
|
const overrides = readOverrides(cwd);
|
|
2545
3035
|
for (const doc of ROOT_DOCS) {
|
|
2546
|
-
const targetPath =
|
|
2547
|
-
if (!
|
|
3036
|
+
const targetPath = join13(cwd, doc);
|
|
3037
|
+
if (!existsSync12(targetPath)) continue;
|
|
2548
3038
|
const currentTemplate = readTemplate(manifest.stack, doc);
|
|
2549
3039
|
if (!currentTemplate) continue;
|
|
2550
|
-
const targetHash = sha256(
|
|
3040
|
+
const targetHash = sha256(readFileSync11(targetPath, "utf8"));
|
|
2551
3041
|
const currentTemplateHash = sha256(currentTemplate);
|
|
2552
3042
|
const installedTemplateHash = manifest.templateHashes?.[doc];
|
|
2553
3043
|
const override = overrides[doc];
|
|
@@ -2839,27 +3329,25 @@ function addMessagingFindings(cwd, findings) {
|
|
|
2839
3329
|
});
|
|
2840
3330
|
}
|
|
2841
3331
|
}
|
|
2842
|
-
function auditProject(cwd) {
|
|
3332
|
+
function auditProject(cwd, options = {}) {
|
|
2843
3333
|
const findings = [];
|
|
2844
3334
|
const manifest = readManifest(cwd);
|
|
2845
3335
|
const packageRepository = isPackageRepository(cwd);
|
|
2846
|
-
const packageSourceMode = packageRepository && !manifest;
|
|
2847
|
-
const docsCwd = packageSourceMode ?
|
|
2848
|
-
if (
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
});
|
|
2862
|
-
}
|
|
3336
|
+
const packageSourceMode = options.packageSource === true || packageRepository && !manifest;
|
|
3337
|
+
const docsCwd = packageSourceMode ? join13(cwd, "templates", "next-supabase") : cwd;
|
|
3338
|
+
if (packageSourceMode) {
|
|
3339
|
+
findings.push({
|
|
3340
|
+
level: "pass",
|
|
3341
|
+
area: "install",
|
|
3342
|
+
message: "Package source repository mode detected; installed-project manifest is not required."
|
|
3343
|
+
});
|
|
3344
|
+
} else if (!manifest) {
|
|
3345
|
+
findings.push({
|
|
3346
|
+
level: "fail",
|
|
3347
|
+
area: "install",
|
|
3348
|
+
message: "Project has no .agent-kit/manifest.json.",
|
|
3349
|
+
remediation: "Run agent-kit init --stack next-supabase."
|
|
3350
|
+
});
|
|
2863
3351
|
} else {
|
|
2864
3352
|
findings.push({
|
|
2865
3353
|
level: "pass",
|
|
@@ -2867,17 +3355,20 @@ function auditProject(cwd) {
|
|
|
2867
3355
|
message: `Agent kit installed at version ${manifest.packageVersion}.`
|
|
2868
3356
|
});
|
|
2869
3357
|
}
|
|
2870
|
-
addTemplateHashFindings(cwd, findings);
|
|
3358
|
+
if (!packageSourceMode) addTemplateHashFindings(cwd, findings);
|
|
2871
3359
|
addAgentRosterFindings(cwd, findings, packageSourceMode ? "rosters/next-supabase-default-council.json" : DEFAULT_AGENT_ROSTER_TARGET);
|
|
2872
3360
|
addSchemaFindings(cwd, findings, packageSourceMode ? "schemas" : ".agent-kit/schemas");
|
|
2873
|
-
|
|
2874
|
-
if (!
|
|
2875
|
-
|
|
3361
|
+
addOrchestratorFindings(cwd, findings, packageSourceMode ? "templates/next-supabase/.agent-kit/orchestrator.json" : DEFAULT_ORCHESTRATOR_TARGET);
|
|
3362
|
+
if (!packageSourceMode) {
|
|
3363
|
+
addCouncilSessionRecordFindings(cwd, findings);
|
|
3364
|
+
if (!packageRepository || existsSync12(join13(cwd, CONTEXT_JSON)) || existsSync12(join13(cwd, COUNCIL_SESSION_DIR))) {
|
|
3365
|
+
addAgentStudioFindings(cwd, findings);
|
|
3366
|
+
}
|
|
2876
3367
|
}
|
|
2877
3368
|
for (const doc of ROOT_DOCS) {
|
|
2878
|
-
const docPath =
|
|
3369
|
+
const docPath = join13(docsCwd, doc);
|
|
2879
3370
|
const displayPath = packageSourceMode ? `templates/next-supabase/${doc}` : doc;
|
|
2880
|
-
if (
|
|
3371
|
+
if (existsSync12(docPath)) {
|
|
2881
3372
|
findings.push({ level: "pass", area: "docs", message: `${displayPath} exists.` });
|
|
2882
3373
|
} else {
|
|
2883
3374
|
findings.push({
|
|
@@ -2895,7 +3386,7 @@ function auditProject(cwd) {
|
|
|
2895
3386
|
addQualityGateFindings(docsCwd, findings);
|
|
2896
3387
|
addUpgradeFindings(docsCwd, findings);
|
|
2897
3388
|
addProjectEvidenceFindings(docsCwd, findings);
|
|
2898
|
-
|
|
3389
|
+
findings.push(...projectRealityRules.evaluate({ cwd, packageRepository, observedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
2899
3390
|
const security = readDoc(docsCwd, "SECURITY.md");
|
|
2900
3391
|
if (!includesAny(security, ["OWASP", "Top 10"])) {
|
|
2901
3392
|
findings.push({
|
|
@@ -2941,125 +3432,7 @@ function auditProject(cwd) {
|
|
|
2941
3432
|
}
|
|
2942
3433
|
return findings;
|
|
2943
3434
|
}
|
|
2944
|
-
function
|
|
2945
|
-
const path = join11(cwd, "package.json");
|
|
2946
|
-
if (!existsSync11(path)) return null;
|
|
2947
|
-
try {
|
|
2948
|
-
return JSON.parse(readFileSync9(path, "utf8"));
|
|
2949
|
-
} catch {
|
|
2950
|
-
return null;
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
function containsLikelySecretForAudit(relativeFile, content) {
|
|
2954
|
-
const normalized = relativeFile.replace(/\\/g, "/");
|
|
2955
|
-
const testSecretFixture = ["sk", "test", "fake", "secret", "value"].join("_");
|
|
2956
|
-
if (normalized.startsWith("tests/") && content.includes(`const fakeSecret = "${testSecretFixture}"`) && content.includes("not.toContain(fakeSecret)")) {
|
|
2957
|
-
return containsLikelySecret(content.split(testSecretFixture).join("[TEST_SECRET_FIXTURE]"));
|
|
2958
|
-
}
|
|
2959
|
-
return containsLikelySecret(content);
|
|
2960
|
-
}
|
|
2961
|
-
function addProjectRealityFindings(cwd, findings, options = {}) {
|
|
2962
|
-
const migrationsDir = join11(cwd, "supabase", "migrations");
|
|
2963
|
-
if (existsSync11(migrationsDir)) {
|
|
2964
|
-
const sqlFiles = listFilesRecursive(migrationsDir).filter((file) => file.endsWith(".sql"));
|
|
2965
|
-
if (sqlFiles.length === 0) {
|
|
2966
|
-
findings.push({
|
|
2967
|
-
level: "warn",
|
|
2968
|
-
area: "project-reality",
|
|
2969
|
-
message: "supabase/migrations exists but contains no SQL migration files.",
|
|
2970
|
-
remediation: "Add versioned SQL migrations or remove the empty migrations directory if Supabase is not in use."
|
|
2971
|
-
});
|
|
2972
|
-
} else {
|
|
2973
|
-
const rlsFiles = sqlFiles.filter((file) => {
|
|
2974
|
-
const content = readFileSync9(join11(migrationsDir, file), "utf8");
|
|
2975
|
-
return /enable\s+row\s+level\s+security/i.test(content);
|
|
2976
|
-
});
|
|
2977
|
-
if (rlsFiles.length === 0) {
|
|
2978
|
-
findings.push({
|
|
2979
|
-
level: "fail",
|
|
2980
|
-
area: "project-reality",
|
|
2981
|
-
message: "No Supabase migration enables row level security.",
|
|
2982
|
-
remediation: "Add `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` (or equivalent) in supabase/migrations before shipping user data."
|
|
2983
|
-
});
|
|
2984
|
-
} else {
|
|
2985
|
-
findings.push({
|
|
2986
|
-
level: "pass",
|
|
2987
|
-
area: "project-reality",
|
|
2988
|
-
message: `Supabase migrations enable RLS in ${rlsFiles.length} file(s).`
|
|
2989
|
-
});
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
const packageJson = readPackageJson2(cwd);
|
|
2994
|
-
if (!packageJson) {
|
|
2995
|
-
findings.push({
|
|
2996
|
-
level: "warn",
|
|
2997
|
-
area: "project-reality",
|
|
2998
|
-
message: "No package.json found to verify test scripts.",
|
|
2999
|
-
remediation: "Add package.json with test, lint, and build scripts appropriate to the stack."
|
|
3000
|
-
});
|
|
3001
|
-
} else {
|
|
3002
|
-
const scripts = packageJson.scripts ?? {};
|
|
3003
|
-
const testScript = scripts.test ?? scripts["test:unit"] ?? scripts["test:ci"];
|
|
3004
|
-
if (!testScript) {
|
|
3005
|
-
findings.push({
|
|
3006
|
-
level: "warn",
|
|
3007
|
-
area: "project-reality",
|
|
3008
|
-
message: "package.json has no test script (test, test:unit, or test:ci).",
|
|
3009
|
-
remediation: "Add a test script and document it in TESTING.md."
|
|
3010
|
-
});
|
|
3011
|
-
} else {
|
|
3012
|
-
findings.push({
|
|
3013
|
-
level: "pass",
|
|
3014
|
-
area: "project-reality",
|
|
3015
|
-
message: "package.json defines a test script."
|
|
3016
|
-
});
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
const trackedSourceFiles = listFilesRecursive(cwd).filter((file) => {
|
|
3020
|
-
if (file.includes("node_modules/") || file.includes(".agent-kit/")) return false;
|
|
3021
|
-
return /\.(ts|tsx|js|jsx|env|json)$/.test(file);
|
|
3022
|
-
});
|
|
3023
|
-
const secretHits = trackedSourceFiles.map((file) => {
|
|
3024
|
-
const content = readFileSync9(join11(cwd, file), "utf8");
|
|
3025
|
-
return containsLikelySecretForAudit(file, content) ? file : null;
|
|
3026
|
-
}).filter((file) => file !== null).slice(0, 5);
|
|
3027
|
-
if (secretHits.length > 0) {
|
|
3028
|
-
findings.push({
|
|
3029
|
-
level: "fail",
|
|
3030
|
-
area: "project-reality",
|
|
3031
|
-
message: `Possible committed secret patterns detected in: ${secretHits.join(", ")}.`,
|
|
3032
|
-
remediation: "Remove secrets from tracked files, rotate exposed credentials, and use environment variables."
|
|
3033
|
-
});
|
|
3034
|
-
} else if (trackedSourceFiles.length > 0) {
|
|
3035
|
-
findings.push({
|
|
3036
|
-
level: "pass",
|
|
3037
|
-
area: "project-reality",
|
|
3038
|
-
message: "No obvious committed secret patterns detected in tracked source files."
|
|
3039
|
-
});
|
|
3040
|
-
}
|
|
3041
|
-
if (options.packageRepository) {
|
|
3042
|
-
findings.push({
|
|
3043
|
-
level: "pass",
|
|
3044
|
-
area: "project-reality",
|
|
3045
|
-
message: "Package source repository mode does not require installed-project context files."
|
|
3046
|
-
});
|
|
3047
|
-
} else if (!existsSync11(join11(cwd, CONTEXT_JSON))) {
|
|
3048
|
-
findings.push({
|
|
3049
|
-
level: "warn",
|
|
3050
|
-
area: "project-reality",
|
|
3051
|
-
message: ".agent-kit/project-context.json is missing.",
|
|
3052
|
-
remediation: "Run agent-kit init or agent-kit context init to create project context."
|
|
3053
|
-
});
|
|
3054
|
-
} else {
|
|
3055
|
-
findings.push({
|
|
3056
|
-
level: "pass",
|
|
3057
|
-
area: "project-reality",
|
|
3058
|
-
message: ".agent-kit/project-context.json exists."
|
|
3059
|
-
});
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
function createReadiness(findings, summary2) {
|
|
3435
|
+
function createAuditReadiness(findings, summary2) {
|
|
3063
3436
|
const nextActions = findings.filter((finding) => finding.level === "fail" || finding.level === "warn").map((finding) => finding.remediation ?? finding.message).filter((value, index, values) => values.indexOf(value) === index).slice(0, 5);
|
|
3064
3437
|
if (summary2.fail > 0) {
|
|
3065
3438
|
return {
|
|
@@ -3088,11 +3461,16 @@ function createReadiness(findings, summary2) {
|
|
|
3088
3461
|
nextActions
|
|
3089
3462
|
};
|
|
3090
3463
|
}
|
|
3091
|
-
function createAuditReport(cwd) {
|
|
3092
|
-
const findings = auditProject(cwd)
|
|
3464
|
+
function createAuditReport(cwd, options = {}) {
|
|
3465
|
+
const findings = auditProject(cwd, options).map((finding) => ({
|
|
3466
|
+
level: finding.level,
|
|
3467
|
+
area: finding.area,
|
|
3468
|
+
message: finding.message,
|
|
3469
|
+
...finding.remediation ? { remediation: finding.remediation } : {}
|
|
3470
|
+
}));
|
|
3093
3471
|
const summary2 = { pass: 0, warn: 0, fail: 0 };
|
|
3094
3472
|
for (const finding of findings) summary2[finding.level] += 1;
|
|
3095
|
-
return { summary: summary2, readiness:
|
|
3473
|
+
return { summary: summary2, readiness: createAuditReadiness(findings, summary2), findings };
|
|
3096
3474
|
}
|
|
3097
3475
|
|
|
3098
3476
|
// src/install/adapter-validate.ts
|
|
@@ -3131,7 +3509,7 @@ function report(target, findings) {
|
|
|
3131
3509
|
}
|
|
3132
3510
|
function readJson(path) {
|
|
3133
3511
|
try {
|
|
3134
|
-
return JSON.parse(
|
|
3512
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3135
3513
|
} catch {
|
|
3136
3514
|
return null;
|
|
3137
3515
|
}
|
|
@@ -3145,24 +3523,24 @@ function isSafeRelativePath(path) {
|
|
|
3145
3523
|
return !normalized.startsWith("/") && !normalized.startsWith("../") && !normalized.includes("/../");
|
|
3146
3524
|
}
|
|
3147
3525
|
function findAntigravityLayout(cwd) {
|
|
3148
|
-
const sourcePlugin =
|
|
3149
|
-
if (
|
|
3526
|
+
const sourcePlugin = join14(cwd, "antigravity", "plugin.json");
|
|
3527
|
+
if (existsSync13(sourcePlugin)) {
|
|
3150
3528
|
return {
|
|
3151
3529
|
mode: "source",
|
|
3152
|
-
pluginRoot:
|
|
3153
|
-
commandsRoot:
|
|
3154
|
-
runtimeSkillsRoot:
|
|
3155
|
-
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")
|
|
3156
3534
|
};
|
|
3157
3535
|
}
|
|
3158
|
-
const installedPlugin =
|
|
3159
|
-
if (
|
|
3536
|
+
const installedPlugin = join14(cwd, ".antigravity", "agent-kit", "plugin.json");
|
|
3537
|
+
if (existsSync13(installedPlugin)) {
|
|
3160
3538
|
return {
|
|
3161
3539
|
mode: "installed",
|
|
3162
|
-
pluginRoot:
|
|
3163
|
-
commandsRoot:
|
|
3164
|
-
runtimeSkillsRoot:
|
|
3165
|
-
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")
|
|
3166
3544
|
};
|
|
3167
3545
|
}
|
|
3168
3546
|
return null;
|
|
@@ -3186,8 +3564,8 @@ function validateAntigravityCommands(layout, findings) {
|
|
|
3186
3564
|
const commandNames = /* @__PURE__ */ new Set();
|
|
3187
3565
|
for (const command of REQUIRED_COMMANDS) {
|
|
3188
3566
|
const relativePath = `${command}.toml`;
|
|
3189
|
-
const path =
|
|
3190
|
-
if (!
|
|
3567
|
+
const path = join14(layout.commandsRoot, relativePath);
|
|
3568
|
+
if (!existsSync13(path)) {
|
|
3191
3569
|
findings.push({
|
|
3192
3570
|
level: "fail",
|
|
3193
3571
|
area: "commands",
|
|
@@ -3196,7 +3574,7 @@ function validateAntigravityCommands(layout, findings) {
|
|
|
3196
3574
|
});
|
|
3197
3575
|
continue;
|
|
3198
3576
|
}
|
|
3199
|
-
const text =
|
|
3577
|
+
const text = readFileSync12(path, "utf8");
|
|
3200
3578
|
addSecretFinding(relativePath, text, findings);
|
|
3201
3579
|
const name = commandField(text, "name");
|
|
3202
3580
|
const description = commandField(text, "description");
|
|
@@ -3253,7 +3631,7 @@ function validateAntigravityCommands(layout, findings) {
|
|
|
3253
3631
|
}
|
|
3254
3632
|
}
|
|
3255
3633
|
function validateAntigravityPlugin(layout, findings) {
|
|
3256
|
-
const pluginPath =
|
|
3634
|
+
const pluginPath = join14(layout.pluginRoot, "plugin.json");
|
|
3257
3635
|
const plugin = readJson(pluginPath);
|
|
3258
3636
|
if (!plugin || !isRecord2(plugin)) {
|
|
3259
3637
|
findings.push({
|
|
@@ -3294,8 +3672,8 @@ function validateAntigravityPlugin(layout, findings) {
|
|
|
3294
3672
|
});
|
|
3295
3673
|
continue;
|
|
3296
3674
|
}
|
|
3297
|
-
const resolved =
|
|
3298
|
-
if (!
|
|
3675
|
+
const resolved = join14(layout.pluginRoot, path);
|
|
3676
|
+
if (!existsSync13(resolved)) {
|
|
3299
3677
|
findings.push({
|
|
3300
3678
|
level: "fail",
|
|
3301
3679
|
area: "manifest",
|
|
@@ -3304,7 +3682,7 @@ function validateAntigravityPlugin(layout, findings) {
|
|
|
3304
3682
|
});
|
|
3305
3683
|
}
|
|
3306
3684
|
}
|
|
3307
|
-
const pluginText =
|
|
3685
|
+
const pluginText = readFileSync12(pluginPath, "utf8");
|
|
3308
3686
|
addSecretFinding("plugin.json", pluginText, findings);
|
|
3309
3687
|
if (Array.isArray(plugin.sourceOfTruth) && plugin.sourceOfTruth.includes("AGENTS.md") && plugin.sourceOfTruth.includes(".agent-kit/agent-roster.json")) {
|
|
3310
3688
|
findings.push({
|
|
@@ -3322,13 +3700,13 @@ function validateAntigravityPlugin(layout, findings) {
|
|
|
3322
3700
|
}
|
|
3323
3701
|
}
|
|
3324
3702
|
function validateRuntimeSkills(cwd, layout, findings) {
|
|
3325
|
-
const canonicalSkillsRoot =
|
|
3703
|
+
const canonicalSkillsRoot = existsSync13(join14(cwd, "skills")) ? join14(cwd, "skills") : join14(cwd, ".agent-kit", "skills");
|
|
3326
3704
|
const canonicalSkillNames = listFilesRecursive(canonicalSkillsRoot).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, ""));
|
|
3327
3705
|
const runtimeSkillFiles = listFilesRecursive(layout.runtimeSkillsRoot).filter((file) => file.endsWith("/SKILL.md") || file === "SKILL.md");
|
|
3328
3706
|
const runtimeSkillNames = runtimeSkillFiles.map((file) => file.split(/[\\/]/)[0]).filter((value) => typeof value === "string" && value.length > 0).filter((value, index, values) => values.indexOf(value) === index);
|
|
3329
3707
|
for (const skillName of canonicalSkillNames) {
|
|
3330
|
-
const runtimePath =
|
|
3331
|
-
if (!
|
|
3708
|
+
const runtimePath = join14(layout.runtimeSkillsRoot, skillName, "SKILL.md");
|
|
3709
|
+
if (!existsSync13(runtimePath)) {
|
|
3332
3710
|
findings.push({
|
|
3333
3711
|
level: "fail",
|
|
3334
3712
|
area: "runtime-skills",
|
|
@@ -3337,7 +3715,7 @@ function validateRuntimeSkills(cwd, layout, findings) {
|
|
|
3337
3715
|
});
|
|
3338
3716
|
continue;
|
|
3339
3717
|
}
|
|
3340
|
-
const text =
|
|
3718
|
+
const text = readFileSync12(runtimePath, "utf8");
|
|
3341
3719
|
addSecretFinding(`${skillName}/SKILL.md`, text, findings);
|
|
3342
3720
|
if (!/^---\nname: .+\ndescription: .+\n---/m.test(text)) {
|
|
3343
3721
|
findings.push({
|
|
@@ -3365,7 +3743,7 @@ function validateRuntimeSkills(cwd, layout, findings) {
|
|
|
3365
3743
|
remediation: "Add canonical skills or remove orphan runtime wrappers."
|
|
3366
3744
|
});
|
|
3367
3745
|
}
|
|
3368
|
-
if (canonicalSkillNames.length > 0 && canonicalSkillNames.every((skillName) =>
|
|
3746
|
+
if (canonicalSkillNames.length > 0 && canonicalSkillNames.every((skillName) => existsSync13(join14(layout.runtimeSkillsRoot, skillName, "SKILL.md")))) {
|
|
3369
3747
|
findings.push({
|
|
3370
3748
|
level: "pass",
|
|
3371
3749
|
area: "runtime-skills",
|
|
@@ -3386,7 +3764,7 @@ function validateAntigravity(cwd) {
|
|
|
3386
3764
|
}
|
|
3387
3765
|
]);
|
|
3388
3766
|
}
|
|
3389
|
-
const adapterDoc =
|
|
3767
|
+
const adapterDoc = existsSync13(layout.adapterDocPath) ? readFileSync12(layout.adapterDocPath, "utf8") : "";
|
|
3390
3768
|
if (!adapterDoc) {
|
|
3391
3769
|
findings.push({
|
|
3392
3770
|
level: "fail",
|
|
@@ -3415,7 +3793,7 @@ function validateAntigravity(cwd) {
|
|
|
3415
3793
|
validateAntigravityCommands(layout, findings);
|
|
3416
3794
|
validateRuntimeSkills(cwd, layout, findings);
|
|
3417
3795
|
if (layout.mode === "source") {
|
|
3418
|
-
const packageJson = readJson(
|
|
3796
|
+
const packageJson = readJson(join14(cwd, "package.json"));
|
|
3419
3797
|
const files = isRecord2(packageJson) && Array.isArray(packageJson.files) ? packageJson.files : [];
|
|
3420
3798
|
for (const requiredFile of ["antigravity", "runtime-skills", "assistant-adapters"]) {
|
|
3421
3799
|
if (!files.includes(requiredFile)) {
|
|
@@ -3439,7 +3817,7 @@ function validateAntigravity(cwd) {
|
|
|
3439
3817
|
}
|
|
3440
3818
|
function validateBasicAdapter(cwd, target) {
|
|
3441
3819
|
const findings = [];
|
|
3442
|
-
const isPackageSource =
|
|
3820
|
+
const isPackageSource = existsSync13(join14(cwd, "package.json")) && existsSync13(join14(cwd, "src")) && existsSync13(join14(cwd, "templates"));
|
|
3443
3821
|
if (isPackageSource) {
|
|
3444
3822
|
const sourcePaths = {
|
|
3445
3823
|
cursor: [
|
|
@@ -3452,8 +3830,8 @@ function validateBasicAdapter(cwd, target) {
|
|
|
3452
3830
|
copilot: ["assistant-adapters/github-copilot-instructions.md", "assistant-adapters/github-next-supabase.instructions.md"]
|
|
3453
3831
|
};
|
|
3454
3832
|
for (const relativePath of sourcePaths[target]) {
|
|
3455
|
-
const path =
|
|
3456
|
-
if (!
|
|
3833
|
+
const path = join14(cwd, relativePath);
|
|
3834
|
+
if (!existsSync13(path)) {
|
|
3457
3835
|
findings.push({
|
|
3458
3836
|
level: "fail",
|
|
3459
3837
|
area: "adapter",
|
|
@@ -3462,7 +3840,7 @@ function validateBasicAdapter(cwd, target) {
|
|
|
3462
3840
|
});
|
|
3463
3841
|
continue;
|
|
3464
3842
|
}
|
|
3465
|
-
const text =
|
|
3843
|
+
const text = readFileSync12(path, "utf8");
|
|
3466
3844
|
addSecretFinding(relativePath, text, findings);
|
|
3467
3845
|
if (!text.includes("AGENTS.md") && !text.includes("MODEL_ROUTING.md")) {
|
|
3468
3846
|
findings.push({
|
|
@@ -3486,8 +3864,8 @@ function validateBasicAdapter(cwd, target) {
|
|
|
3486
3864
|
return report(target, findings);
|
|
3487
3865
|
}
|
|
3488
3866
|
function readAssistantAdaptersDoc(cwd) {
|
|
3489
|
-
const path =
|
|
3490
|
-
return
|
|
3867
|
+
const path = join14(cwd, "ASSISTANT_ADAPTERS.md");
|
|
3868
|
+
return existsSync13(path) ? readFileSync12(path, "utf8") : "";
|
|
3491
3869
|
}
|
|
3492
3870
|
function adaptersRowIsActive(doc, toolLabel) {
|
|
3493
3871
|
return assistantAdapterRowIsActive(doc, toolLabel);
|
|
@@ -3496,8 +3874,8 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3496
3874
|
const findings = [];
|
|
3497
3875
|
const adaptersDoc = readAssistantAdaptersDoc(cwd);
|
|
3498
3876
|
if (target === "cursor") {
|
|
3499
|
-
const rulesPath =
|
|
3500
|
-
if (!
|
|
3877
|
+
const rulesPath = join14(cwd, ".cursor/rules/cursor-agent-kit.mdc");
|
|
3878
|
+
if (!existsSync13(rulesPath)) {
|
|
3501
3879
|
findings.push({
|
|
3502
3880
|
level: "fail",
|
|
3503
3881
|
area: "adapter",
|
|
@@ -3505,10 +3883,10 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3505
3883
|
remediation: "Run agent-kit init or agent-kit init --activate cursor."
|
|
3506
3884
|
});
|
|
3507
3885
|
} else {
|
|
3508
|
-
addSecretFinding(".cursor/rules/cursor-agent-kit.mdc",
|
|
3886
|
+
addSecretFinding(".cursor/rules/cursor-agent-kit.mdc", readFileSync12(rulesPath, "utf8"), findings);
|
|
3509
3887
|
}
|
|
3510
|
-
const plannerAgent =
|
|
3511
|
-
if (
|
|
3888
|
+
const plannerAgent = join14(cwd, ".cursor/agents/planner.md");
|
|
3889
|
+
if (existsSync13(plannerAgent)) {
|
|
3512
3890
|
findings.push({
|
|
3513
3891
|
level: "pass",
|
|
3514
3892
|
area: "adapter",
|
|
@@ -3522,8 +3900,8 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3522
3900
|
remediation: "Run agent-kit init --activate cursor to generate council subagents from the roster."
|
|
3523
3901
|
});
|
|
3524
3902
|
}
|
|
3525
|
-
const skillSample =
|
|
3526
|
-
if (
|
|
3903
|
+
const skillSample = join14(cwd, ".cursor/skills/planning-council/SKILL.md");
|
|
3904
|
+
if (existsSync13(skillSample)) {
|
|
3527
3905
|
findings.push({
|
|
3528
3906
|
level: "pass",
|
|
3529
3907
|
area: "adapter",
|
|
@@ -3532,8 +3910,8 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3532
3910
|
}
|
|
3533
3911
|
}
|
|
3534
3912
|
if (target === "claude") {
|
|
3535
|
-
const plannerAgent =
|
|
3536
|
-
if (!
|
|
3913
|
+
const plannerAgent = join14(cwd, ".claude/agents/planner.md");
|
|
3914
|
+
if (!existsSync13(plannerAgent)) {
|
|
3537
3915
|
findings.push({
|
|
3538
3916
|
level: "fail",
|
|
3539
3917
|
area: "adapter",
|
|
@@ -3543,8 +3921,8 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3543
3921
|
}
|
|
3544
3922
|
}
|
|
3545
3923
|
if (target === "codex") {
|
|
3546
|
-
const configPath =
|
|
3547
|
-
if (!
|
|
3924
|
+
const configPath = join14(cwd, ".codex/config.toml");
|
|
3925
|
+
if (!existsSync13(configPath)) {
|
|
3548
3926
|
findings.push({
|
|
3549
3927
|
level: "fail",
|
|
3550
3928
|
area: "adapter",
|
|
@@ -3552,8 +3930,8 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3552
3930
|
remediation: "Run agent-kit init --activate codex."
|
|
3553
3931
|
});
|
|
3554
3932
|
}
|
|
3555
|
-
const plannerAgent =
|
|
3556
|
-
if (
|
|
3933
|
+
const plannerAgent = join14(cwd, ".codex/agents/planner.toml");
|
|
3934
|
+
if (existsSync13(plannerAgent)) {
|
|
3557
3935
|
findings.push({
|
|
3558
3936
|
level: "pass",
|
|
3559
3937
|
area: "adapter",
|
|
@@ -3569,8 +3947,8 @@ function validateInstalledIdeAdapter(cwd, target) {
|
|
|
3569
3947
|
}
|
|
3570
3948
|
}
|
|
3571
3949
|
if (target === "copilot") {
|
|
3572
|
-
const instructions =
|
|
3573
|
-
if (!
|
|
3950
|
+
const instructions = join14(cwd, ".github/copilot-instructions.md");
|
|
3951
|
+
if (!existsSync13(instructions)) {
|
|
3574
3952
|
findings.push({
|
|
3575
3953
|
level: "fail",
|
|
3576
3954
|
area: "adapter",
|
|
@@ -3594,7 +3972,7 @@ function validateAdapter(cwd, target = "antigravity") {
|
|
|
3594
3972
|
}
|
|
3595
3973
|
function validatePackage(cwd) {
|
|
3596
3974
|
const findings = [];
|
|
3597
|
-
const sourceMode =
|
|
3975
|
+
const sourceMode = existsSync13(join14(cwd, "package.json")) && existsSync13(join14(cwd, "src")) && existsSync13(join14(cwd, "templates"));
|
|
3598
3976
|
if (!sourceMode) {
|
|
3599
3977
|
return report("package", [
|
|
3600
3978
|
{
|
|
@@ -3606,9 +3984,40 @@ function validatePackage(cwd) {
|
|
|
3606
3984
|
]);
|
|
3607
3985
|
}
|
|
3608
3986
|
findings.push(...validateAntigravity(cwd).findings);
|
|
3987
|
+
const runtimeRequired = [
|
|
3988
|
+
"packages/runtime/package.json",
|
|
3989
|
+
"packages/runtime/README.md",
|
|
3990
|
+
"packages/runtime/CHANGELOG.md",
|
|
3991
|
+
"packages/runtime/LICENSE",
|
|
3992
|
+
"packages/runtime/src/index.ts"
|
|
3993
|
+
];
|
|
3994
|
+
for (const target of runtimeRequired) {
|
|
3995
|
+
if (!existsSync13(join14(cwd, target))) {
|
|
3996
|
+
findings.push({ level: "fail", area: "runtime-package", message: `${target} is missing.` });
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
const runtimeManifest = readJson(join14(cwd, "packages", "runtime", "package.json"));
|
|
4000
|
+
if (!isRecord2(runtimeManifest) || runtimeManifest.name !== "@appsforgood/agent-kit-runtime") {
|
|
4001
|
+
findings.push({ level: "fail", area: "runtime-package", message: "Runtime package name is missing or invalid." });
|
|
4002
|
+
} else {
|
|
4003
|
+
const publishConfig = isRecord2(runtimeManifest.publishConfig) ? runtimeManifest.publishConfig : {};
|
|
4004
|
+
const files = Array.isArray(runtimeManifest.files) ? runtimeManifest.files : [];
|
|
4005
|
+
if (runtimeManifest.license !== "MIT") {
|
|
4006
|
+
findings.push({ level: "fail", area: "runtime-package", message: "Runtime package must declare the MIT license." });
|
|
4007
|
+
}
|
|
4008
|
+
if (publishConfig.access !== "public" || publishConfig.provenance !== true) {
|
|
4009
|
+
findings.push({ level: "fail", area: "runtime-package", message: "Runtime package must require public provenance publishing." });
|
|
4010
|
+
}
|
|
4011
|
+
for (const required of ["dist", "README.md", "CHANGELOG.md", "LICENSE"]) {
|
|
4012
|
+
if (!files.includes(required)) findings.push({ level: "fail", area: "runtime-package", message: `Runtime package files omits ${required}.` });
|
|
4013
|
+
}
|
|
4014
|
+
if (findings.every((finding) => finding.area !== "runtime-package" || finding.level !== "fail")) {
|
|
4015
|
+
findings.push({ level: "pass", area: "runtime-package", message: "Runtime package metadata and public artifacts are complete." });
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
3609
4018
|
for (const doc of ["README.md", "DOCS.md", "SPEC.md", "DECISIONS.md", "QUALITY_GATES.md", "TESTING.md", "UPGRADE.md"]) {
|
|
3610
|
-
const path =
|
|
3611
|
-
const text =
|
|
4019
|
+
const path = join14(cwd, doc);
|
|
4020
|
+
const text = existsSync13(path) ? readFileSync12(path, "utf8") : "";
|
|
3612
4021
|
const lower = text.toLowerCase();
|
|
3613
4022
|
if (!lower.includes("antigravity") && !lower.includes("runtime command") && !lower.includes("runtime adapter")) {
|
|
3614
4023
|
findings.push({
|
|
@@ -3625,7 +4034,7 @@ function validatePackage(cwd) {
|
|
|
3625
4034
|
"examples/next-supabase-installed/.agent-kit/manifest.json",
|
|
3626
4035
|
"examples/next-supabase-installed/audit-output.json"
|
|
3627
4036
|
]) {
|
|
3628
|
-
if (!
|
|
4037
|
+
if (!existsSync13(join14(cwd, examplePath))) {
|
|
3629
4038
|
findings.push({
|
|
3630
4039
|
level: "fail",
|
|
3631
4040
|
area: "examples",
|
|
@@ -3634,7 +4043,7 @@ function validatePackage(cwd) {
|
|
|
3634
4043
|
});
|
|
3635
4044
|
}
|
|
3636
4045
|
}
|
|
3637
|
-
const auditReport = createAuditReport(cwd);
|
|
4046
|
+
const auditReport = createAuditReport(cwd, { packageSource: true });
|
|
3638
4047
|
if (auditReport.summary.fail > 0) {
|
|
3639
4048
|
findings.push({
|
|
3640
4049
|
level: "fail",
|
|
@@ -3659,118 +4068,227 @@ function validatePackage(cwd) {
|
|
|
3659
4068
|
return report("package", findings);
|
|
3660
4069
|
}
|
|
3661
4070
|
|
|
3662
|
-
// src/install/
|
|
3663
|
-
import {
|
|
3664
|
-
import {
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
const
|
|
3668
|
-
const
|
|
3669
|
-
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
|
+
}
|
|
3670
4089
|
}
|
|
3671
|
-
function
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
4090
|
+
function validSuppression(suppression, now) {
|
|
4091
|
+
if (!suppression?.reason?.trim() || !suppression.owner?.trim() || !suppression.reviewedAt?.trim()) return false;
|
|
4092
|
+
if (suppression.expiresAt && Date.parse(suppression.expiresAt) <= now) return false;
|
|
4093
|
+
return true;
|
|
4094
|
+
}
|
|
4095
|
+
function createAuditReportV2(cwd) {
|
|
4096
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4097
|
+
const suppressions = loadSuppressions(cwd);
|
|
4098
|
+
const findings = auditProject(cwd).map((finding) => {
|
|
4099
|
+
const ruleId = finding.ruleId ?? fallbackRuleId(finding);
|
|
4100
|
+
const suppression = suppressions[ruleId];
|
|
4101
|
+
const suppressed = finding.level !== "pass" && validSuppression(suppression, Date.now());
|
|
4102
|
+
return {
|
|
4103
|
+
...finding,
|
|
4104
|
+
ruleId,
|
|
4105
|
+
ruleVersion: finding.ruleVersion ?? "1.0.0",
|
|
4106
|
+
confidence: finding.confidence ?? "low",
|
|
4107
|
+
evidence: finding.evidence ?? [],
|
|
4108
|
+
...suppressed ? { suppressed: true, suppressionReason: suppression.reason } : {}
|
|
4109
|
+
};
|
|
4110
|
+
});
|
|
4111
|
+
const activeFindings = findings.filter((finding) => !finding.suppressed);
|
|
4112
|
+
const summary2 = { pass: 0, warn: 0, fail: 0, suppressed: findings.length - activeFindings.length };
|
|
4113
|
+
for (const finding of activeFindings) summary2[finding.level] += 1;
|
|
4114
|
+
return {
|
|
4115
|
+
schemaVersion: 2,
|
|
4116
|
+
generatedAt,
|
|
4117
|
+
tool: { name: "agent-kit", version: PACKAGE_VERSION },
|
|
4118
|
+
root: ".",
|
|
4119
|
+
summary: summary2,
|
|
4120
|
+
readiness: createAuditReadiness(activeFindings, summary2),
|
|
4121
|
+
findings
|
|
3678
4122
|
};
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
4123
|
+
}
|
|
4124
|
+
function auditReportToSarif(report2) {
|
|
4125
|
+
const rules = [...new Map(report2.findings.map((finding) => [finding.ruleId, finding])).values()].map((finding) => ({
|
|
4126
|
+
id: finding.ruleId,
|
|
4127
|
+
name: finding.ruleId,
|
|
4128
|
+
shortDescription: { text: finding.message },
|
|
4129
|
+
...finding.helpUri ? { helpUri: finding.helpUri } : {},
|
|
4130
|
+
properties: { area: finding.area, ruleVersion: finding.ruleVersion, confidence: finding.confidence, fixable: Boolean(finding.fixable) }
|
|
4131
|
+
}));
|
|
4132
|
+
const results = report2.findings.filter((finding) => finding.level !== "pass").map((finding) => {
|
|
4133
|
+
const evidence = finding.evidence.find((item) => item.path);
|
|
4134
|
+
const locations = evidence?.path ? [
|
|
4135
|
+
{
|
|
4136
|
+
physicalLocation: {
|
|
4137
|
+
artifactLocation: { uri: evidence.path.replace(/\\/g, "/") },
|
|
4138
|
+
...evidence.line ? { region: { startLine: evidence.line, ...evidence.column ? { startColumn: evidence.column } : {} } } : {}
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
] : [];
|
|
4142
|
+
return {
|
|
4143
|
+
ruleId: finding.ruleId,
|
|
4144
|
+
level: finding.level === "fail" ? "error" : "warning",
|
|
4145
|
+
message: { text: finding.message },
|
|
4146
|
+
...locations.length > 0 ? { locations } : {},
|
|
4147
|
+
...finding.suppressed ? { suppressions: [{ kind: "external", justification: finding.suppressionReason }] } : {},
|
|
4148
|
+
properties: { area: finding.area, confidence: finding.confidence, remediation: finding.remediation ?? "" }
|
|
4149
|
+
};
|
|
4150
|
+
});
|
|
4151
|
+
return {
|
|
4152
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
4153
|
+
version: "2.1.0",
|
|
4154
|
+
runs: [
|
|
4155
|
+
{
|
|
4156
|
+
tool: {
|
|
4157
|
+
driver: {
|
|
4158
|
+
name: "agent-kit",
|
|
4159
|
+
version: report2.tool.version,
|
|
4160
|
+
informationUri: "https://github.com/lukey662/agentsandskills",
|
|
4161
|
+
rules
|
|
4162
|
+
}
|
|
4163
|
+
},
|
|
4164
|
+
results
|
|
4165
|
+
}
|
|
4166
|
+
]
|
|
3695
4167
|
};
|
|
3696
|
-
for (const doc of ROOT_DOCS) {
|
|
3697
|
-
const target = join13(cwd, doc);
|
|
3698
|
-
const template = join13(templateRoot, doc);
|
|
3699
|
-
const status = statusForTextFile(target, template);
|
|
3700
|
-
if (status === "missing") {
|
|
3701
|
-
result.missing.push(doc);
|
|
3702
|
-
result.preview.wouldCreate.push(doc);
|
|
3703
|
-
continue;
|
|
3704
|
-
}
|
|
3705
|
-
if (status === "unchanged") result.unchanged.push(doc);
|
|
3706
|
-
else {
|
|
3707
|
-
result.changed.push(doc);
|
|
3708
|
-
result.preview.wouldWriteConflicts.push(doc);
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
result.agentRoster = statusForTextFile(join13(cwd, DEFAULT_AGENT_ROSTER_TARGET), join13(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE));
|
|
3712
|
-
if (result.agentRoster === "missing") {
|
|
3713
|
-
result.preview.wouldCreate.push(DEFAULT_AGENT_ROSTER_TARGET);
|
|
3714
|
-
result.preview.wouldCreateAgentRoster = true;
|
|
3715
|
-
}
|
|
3716
|
-
if (result.agentRoster === "changed") {
|
|
3717
|
-
result.preview.wouldWriteConflicts.push(DEFAULT_AGENT_ROSTER_TARGET);
|
|
3718
|
-
result.preview.wouldWriteAgentRosterConflict = true;
|
|
3719
|
-
}
|
|
3720
|
-
result.modelRouting = statusForTextFile(join13(cwd, DEFAULT_MODEL_ROUTING_TARGET), join13(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE));
|
|
3721
|
-
if (result.modelRouting === "missing") {
|
|
3722
|
-
result.preview.wouldCreate.push(DEFAULT_MODEL_ROUTING_TARGET);
|
|
3723
|
-
result.preview.wouldCreateModelRouting = true;
|
|
3724
|
-
}
|
|
3725
|
-
if (result.modelRouting === "changed") {
|
|
3726
|
-
result.preview.wouldWriteConflicts.push(DEFAULT_MODEL_ROUTING_TARGET);
|
|
3727
|
-
result.preview.wouldWriteModelRoutingConflict = true;
|
|
3728
|
-
}
|
|
3729
|
-
for (const folder of LIBRARY_FOLDERS) {
|
|
3730
|
-
const target = join13(cwd, ".agent-kit", folder);
|
|
3731
|
-
if (existsSync13(target)) libraryFolders.present.push(folder);
|
|
3732
|
-
else libraryFolders.missing.push(folder);
|
|
3733
|
-
}
|
|
3734
|
-
return result;
|
|
3735
4168
|
}
|
|
3736
4169
|
|
|
3737
|
-
// src/install/
|
|
3738
|
-
import { existsSync as
|
|
3739
|
-
import { join as
|
|
3740
|
-
|
|
3741
|
-
|
|
4170
|
+
// src/install/diff.ts
|
|
4171
|
+
import { existsSync as existsSync16 } from "fs";
|
|
4172
|
+
import { join as join16 } from "path";
|
|
4173
|
+
|
|
4174
|
+
// src/install/file-update-plan.ts
|
|
4175
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
4176
|
+
function planFileUpdate(input) {
|
|
4177
|
+
const sourceContent = readFileSync14(input.sourcePath, "utf8");
|
|
3742
4178
|
const sourceHash = sha256(sourceContent);
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
return { target: input.target, action: "created", reason: "File is missing locally.", sourceContent };
|
|
4179
|
+
if (!existsSync15(input.targetPath)) {
|
|
4180
|
+
return { target: input.target, action: "created", reason: "File is missing locally.", sourceContent, sourceHash };
|
|
3746
4181
|
}
|
|
3747
|
-
const
|
|
4182
|
+
const localContent = readFileSync14(input.targetPath, "utf8");
|
|
4183
|
+
const localHash = sha256(localContent);
|
|
3748
4184
|
if (localHash === sourceHash) {
|
|
3749
|
-
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
|
+
};
|
|
3750
4193
|
}
|
|
3751
4194
|
if (input.installedHash && localHash === input.installedHash) {
|
|
3752
|
-
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
|
+
};
|
|
3753
4203
|
}
|
|
3754
4204
|
if (input.force) {
|
|
3755
|
-
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
|
+
};
|
|
3756
4213
|
}
|
|
3757
4214
|
if (input.installedHash && input.installedHash === sourceHash) {
|
|
3758
|
-
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
|
+
};
|
|
3759
4223
|
}
|
|
3760
4224
|
return {
|
|
3761
4225
|
target: input.target,
|
|
3762
4226
|
action: "conflict",
|
|
3763
|
-
reason: "File is locally customized and the
|
|
3764
|
-
sourceContent
|
|
4227
|
+
reason: "File is locally customized and the package asset changed; review the proposed content.",
|
|
4228
|
+
sourceContent,
|
|
4229
|
+
sourceHash,
|
|
4230
|
+
localContent
|
|
3765
4231
|
};
|
|
3766
4232
|
}
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
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
|
+
};
|
|
3773
4287
|
}
|
|
4288
|
+
|
|
4289
|
+
// src/install/update.ts
|
|
4290
|
+
import { existsSync as existsSync17 } from "fs";
|
|
4291
|
+
import { join as join17 } from "path";
|
|
3774
4292
|
function updateProject(options) {
|
|
3775
4293
|
const cwd = options.cwd;
|
|
3776
4294
|
const force = Boolean(options.force);
|
|
@@ -3783,14 +4301,14 @@ function updateProject(options) {
|
|
|
3783
4301
|
const initResult = initProject({ cwd, force });
|
|
3784
4302
|
const files2 = [
|
|
3785
4303
|
...initResult.copied.map((target) => ({ target, action: "created", reason: "Installed by init fallback." })),
|
|
3786
|
-
...initResult.unchanged.map((target) => ({ target, action: "unchanged", reason: "Already matched the
|
|
4304
|
+
...initResult.unchanged.map((target) => ({ target, action: "unchanged", reason: "Already matched the package asset." })),
|
|
3787
4305
|
...initResult.overwritten.map((target) => ({ target, action: "overwritten", reason: "Overwritten by init --force fallback." })),
|
|
3788
4306
|
...initResult.conflicts.map((entry) => {
|
|
3789
4307
|
const [target, conflictPath] = entry.split(" -> ");
|
|
3790
4308
|
return {
|
|
3791
4309
|
target: target ?? entry,
|
|
3792
4310
|
action: "conflict",
|
|
3793
|
-
reason: "Local file differed from the
|
|
4311
|
+
reason: "Local file differed from the package asset during init fallback.",
|
|
3794
4312
|
...conflictPath ? { conflictPath } : {}
|
|
3795
4313
|
};
|
|
3796
4314
|
})
|
|
@@ -3805,66 +4323,53 @@ function updateProject(options) {
|
|
|
3805
4323
|
}
|
|
3806
4324
|
const packageRoot = findPackageRoot();
|
|
3807
4325
|
const stack = manifest.stack ?? "next-supabase";
|
|
3808
|
-
const templateRoot =
|
|
3809
|
-
if (!
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
templateHashes[doc] = sha256(readFileSync12(sourcePath, "utf8"));
|
|
3818
|
-
plans.push(
|
|
3819
|
-
planFileUpdate(cwd, {
|
|
3820
|
-
target: doc,
|
|
3821
|
-
sourcePath,
|
|
3822
|
-
installedHash: manifest.templateHashes?.[doc],
|
|
3823
|
-
force
|
|
3824
|
-
})
|
|
3825
|
-
);
|
|
3826
|
-
}
|
|
3827
|
-
for (const adapter2 of CURSOR_ADAPTER_FILES) {
|
|
3828
|
-
plans.push(
|
|
3829
|
-
planFileUpdate(cwd, {
|
|
3830
|
-
target: adapter2.target,
|
|
3831
|
-
sourcePath: join14(packageRoot, adapter2.source),
|
|
3832
|
-
installedHash: void 0,
|
|
3833
|
-
force
|
|
3834
|
-
})
|
|
3835
|
-
);
|
|
3836
|
-
}
|
|
3837
|
-
plans.push(
|
|
3838
|
-
planFileUpdate(cwd, {
|
|
3839
|
-
target: DEFAULT_AGENT_ROSTER_TARGET,
|
|
3840
|
-
sourcePath: join14(packageRoot, DEFAULT_AGENT_ROSTER_SOURCE),
|
|
3841
|
-
installedHash: void 0,
|
|
3842
|
-
force
|
|
3843
|
-
}),
|
|
3844
|
-
planFileUpdate(cwd, {
|
|
3845
|
-
target: DEFAULT_MODEL_ROUTING_TARGET,
|
|
3846
|
-
sourcePath: join14(packageRoot, DEFAULT_MODEL_ROUTING_SOURCE),
|
|
3847
|
-
installedHash: void 0,
|
|
4326
|
+
const templateRoot = join17(packageRoot, "templates", stack);
|
|
4327
|
+
if (!existsSync17(templateRoot)) throw new Error(`Unsupported stack profile in manifest: ${stack}`);
|
|
4328
|
+
const assets = listManagedAssets(packageRoot, stack);
|
|
4329
|
+
const plans = assets.map(
|
|
4330
|
+
(asset) => planFileUpdate({
|
|
4331
|
+
target: asset.target,
|
|
4332
|
+
sourcePath: asset.sourcePath,
|
|
4333
|
+
targetPath: resolveInside(cwd, asset.target),
|
|
4334
|
+
installedHash: manifest.assetHashes?.[asset.target] ?? manifest.templateHashes?.[asset.target],
|
|
3848
4335
|
force
|
|
3849
4336
|
})
|
|
3850
4337
|
);
|
|
4338
|
+
const files = [];
|
|
3851
4339
|
for (const plan of plans) {
|
|
3852
|
-
const {
|
|
4340
|
+
const result = { target: plan.target, action: plan.action, reason: plan.reason };
|
|
3853
4341
|
if (!dryRun) {
|
|
3854
4342
|
if (plan.action === "created" || plan.action === "updated" || plan.action === "overwritten") {
|
|
3855
|
-
writeText(resolveInside(cwd, plan.target), sourceContent);
|
|
4343
|
+
writeText(resolveInside(cwd, plan.target), plan.sourceContent);
|
|
3856
4344
|
} else if (plan.action === "conflict") {
|
|
3857
|
-
|
|
4345
|
+
const proposal = writeConflictProposal(cwd, plan.target, plan.sourceContent, {
|
|
4346
|
+
currentContent: plan.localContent,
|
|
4347
|
+
reason: plan.reason,
|
|
4348
|
+
sourceVersion: PACKAGE_VERSION
|
|
4349
|
+
});
|
|
4350
|
+
result.conflictPath = proposal.conflictPath;
|
|
3858
4351
|
}
|
|
3859
4352
|
}
|
|
3860
|
-
files.push(
|
|
4353
|
+
files.push(result);
|
|
4354
|
+
}
|
|
4355
|
+
const currentTargets = new Set(assets.map((asset) => asset.target));
|
|
4356
|
+
for (const staleTarget of Object.keys(manifest.assetHashes ?? {}).filter((target) => !currentTargets.has(target)).sort()) {
|
|
4357
|
+
if (!existsSync17(resolveInside(cwd, staleTarget))) continue;
|
|
4358
|
+
files.push({
|
|
4359
|
+
target: staleTarget,
|
|
4360
|
+
action: "kept-local",
|
|
4361
|
+
reason: "The asset is no longer shipped; it was retained for explicit manual removal."
|
|
4362
|
+
});
|
|
3861
4363
|
}
|
|
4364
|
+
const refreshedFolders = [
|
|
4365
|
+
...new Set(
|
|
4366
|
+
assets.filter((asset, index) => asset.libraryFolder && ["created", "updated", "overwritten"].includes(plans[index]?.action ?? "")).map((asset) => asset.libraryFolder)
|
|
4367
|
+
)
|
|
4368
|
+
].sort();
|
|
3862
4369
|
if (!dryRun) {
|
|
3863
|
-
|
|
3864
|
-
for (const folder of LIBRARY_FOLDERS) {
|
|
3865
|
-
copyDirectory(join14(packageRoot, folder), join14(cwd, ".agent-kit", folder));
|
|
3866
|
-
}
|
|
4370
|
+
const assetHashes = hashManagedAssets(assets);
|
|
3867
4371
|
const updatedManifest = {
|
|
4372
|
+
schemaVersion: 2,
|
|
3868
4373
|
packageName: PACKAGE_NAME,
|
|
3869
4374
|
packageVersion: PACKAGE_VERSION,
|
|
3870
4375
|
stack,
|
|
@@ -3872,17 +4377,18 @@ function updateProject(options) {
|
|
|
3872
4377
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3873
4378
|
docs: [...ROOT_DOCS],
|
|
3874
4379
|
libraryFolders: [...LIBRARY_FOLDERS],
|
|
3875
|
-
agentRoster: DEFAULT_AGENT_ROSTER_TARGET,
|
|
3876
|
-
modelRouting: DEFAULT_MODEL_ROUTING_TARGET,
|
|
3877
|
-
templateHashes
|
|
4380
|
+
agentRoster: manifest.agentRoster ?? DEFAULT_AGENT_ROSTER_TARGET,
|
|
4381
|
+
modelRouting: manifest.modelRouting ?? DEFAULT_MODEL_ROUTING_TARGET,
|
|
4382
|
+
templateHashes: Object.fromEntries(ROOT_DOCS.map((doc) => [doc, assetHashes[doc] ?? ""])),
|
|
4383
|
+
assetHashes
|
|
3878
4384
|
};
|
|
3879
|
-
writeText(
|
|
4385
|
+
writeText(join17(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(updatedManifest, null, 2)}
|
|
3880
4386
|
`);
|
|
3881
4387
|
}
|
|
3882
4388
|
return {
|
|
3883
4389
|
dryRun,
|
|
3884
4390
|
files,
|
|
3885
|
-
libraryFoldersRefreshed:
|
|
4391
|
+
libraryFoldersRefreshed: refreshedFolders,
|
|
3886
4392
|
manifestPath: ".agent-kit/manifest.json",
|
|
3887
4393
|
summary: summarize(files)
|
|
3888
4394
|
};
|
|
@@ -3902,8 +4408,8 @@ function summarize(files) {
|
|
|
3902
4408
|
|
|
3903
4409
|
// src/research/discover.ts
|
|
3904
4410
|
import { Octokit } from "@octokit/rest";
|
|
3905
|
-
import { readFileSync as
|
|
3906
|
-
import { join as
|
|
4411
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
4412
|
+
import { join as join18 } from "path";
|
|
3907
4413
|
|
|
3908
4414
|
// src/research/config.ts
|
|
3909
4415
|
import { z as z2 } from "zod";
|
|
@@ -3928,8 +4434,8 @@ var researchConfigSchema = z2.object({
|
|
|
3928
4434
|
// src/research/discover.ts
|
|
3929
4435
|
async function discoverRepos(options) {
|
|
3930
4436
|
const packageRoot = findPackageRoot();
|
|
3931
|
-
const configPath =
|
|
3932
|
-
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")));
|
|
3933
4439
|
const token = options.token ?? process.env.GITHUB_TOKEN;
|
|
3934
4440
|
if (!token) {
|
|
3935
4441
|
throw new Error("GITHUB_TOKEN is required for GitHub API research discovery.");
|
|
@@ -3990,20 +4496,20 @@ async function discoverRepos(options) {
|
|
|
3990
4496
|
}
|
|
3991
4497
|
}
|
|
3992
4498
|
const candidates = [...deduped.values()].slice(0, maxRepos);
|
|
3993
|
-
const output = options.output ??
|
|
4499
|
+
const output = options.output ?? join18(options.cwd, "research", "repo-candidates.json");
|
|
3994
4500
|
writeText(output, `${JSON.stringify(candidates, null, 2)}
|
|
3995
4501
|
`);
|
|
3996
4502
|
return candidates;
|
|
3997
4503
|
}
|
|
3998
4504
|
|
|
3999
4505
|
// src/research/scan.ts
|
|
4000
|
-
import { existsSync as
|
|
4001
|
-
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";
|
|
4002
4508
|
import { simpleGit } from "simple-git";
|
|
4003
4509
|
|
|
4004
4510
|
// src/research/analyze.ts
|
|
4005
|
-
import { existsSync as
|
|
4006
|
-
import { join as
|
|
4511
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
4512
|
+
import { join as join19 } from "path";
|
|
4007
4513
|
function normalizeRelativePath(file) {
|
|
4008
4514
|
return file.replace(/\\/g, "/");
|
|
4009
4515
|
}
|
|
@@ -4011,8 +4517,8 @@ function hasFile(files, matcher) {
|
|
|
4011
4517
|
return files.some((file) => matcher.test(normalizeRelativePath(file)));
|
|
4012
4518
|
}
|
|
4013
4519
|
function fileText(root, file) {
|
|
4014
|
-
const path =
|
|
4015
|
-
return
|
|
4520
|
+
const path = join19(root, file);
|
|
4521
|
+
return existsSync18(path) ? readFileSync16(path, "utf8") : "";
|
|
4016
4522
|
}
|
|
4017
4523
|
function textIncludes(root, files, matcher, terms) {
|
|
4018
4524
|
const lowerTerms = terms.map((term) => term.toLowerCase());
|
|
@@ -4143,34 +4649,34 @@ ${finding.impactOnKit.map((item) => `- ${item}`).join("\n")}
|
|
|
4143
4649
|
`;
|
|
4144
4650
|
}
|
|
4145
4651
|
async function scanRepos(options) {
|
|
4146
|
-
const candidatesPath = options.candidatesPath ??
|
|
4147
|
-
if (!
|
|
4652
|
+
const candidatesPath = options.candidatesPath ?? join20(options.cwd, "research", "repo-candidates.json");
|
|
4653
|
+
if (!existsSync19(candidatesPath)) {
|
|
4148
4654
|
throw new Error(`Candidates file not found: ${candidatesPath}`);
|
|
4149
4655
|
}
|
|
4150
|
-
const candidates = JSON.parse(
|
|
4151
|
-
const workdir = options.workdir ??
|
|
4656
|
+
const candidates = JSON.parse(readFileSync17(candidatesPath, "utf8"));
|
|
4657
|
+
const workdir = options.workdir ?? join20(options.cwd, "research", "workdir");
|
|
4152
4658
|
mkdirSync2(workdir, { recursive: true });
|
|
4153
|
-
mkdirSync2(
|
|
4659
|
+
mkdirSync2(join20(options.cwd, "research", "findings"), { recursive: true });
|
|
4154
4660
|
const findings = [];
|
|
4155
4661
|
const git = simpleGit();
|
|
4156
4662
|
for (const candidate of candidates) {
|
|
4157
4663
|
const repoSlug = candidate.fullName.replace("/", "__");
|
|
4158
|
-
const repoPath =
|
|
4159
|
-
if (
|
|
4664
|
+
const repoPath = join20(workdir, repoSlug);
|
|
4665
|
+
if (existsSync19(repoPath)) rmSync2(repoPath, { recursive: true, force: true });
|
|
4160
4666
|
await git.raw(["clone", "--depth", "1", candidate.htmlUrl, repoPath]);
|
|
4161
4667
|
const finding = analyzeRepository(candidate, repoPath);
|
|
4162
4668
|
findings.push(finding);
|
|
4163
|
-
writeText(
|
|
4669
|
+
writeText(join20(options.cwd, "research", "findings", `${repoSlug}.md`), findingToMarkdown(finding));
|
|
4164
4670
|
if (!options.keepClones) {
|
|
4165
|
-
|
|
4671
|
+
rmSync2(repoPath, { recursive: true, force: true });
|
|
4166
4672
|
}
|
|
4167
4673
|
}
|
|
4168
4674
|
return findings;
|
|
4169
4675
|
}
|
|
4170
4676
|
|
|
4171
4677
|
// src/research/summarize.ts
|
|
4172
|
-
import { existsSync as
|
|
4173
|
-
import { join as
|
|
4678
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18, readdirSync as readdirSync3 } from "fs";
|
|
4679
|
+
import { join as join21 } from "path";
|
|
4174
4680
|
var SUMMARY_TARGETS = {
|
|
4175
4681
|
"nextjs-patterns": {
|
|
4176
4682
|
title: "Next.js Patterns",
|
|
@@ -4273,12 +4779,12 @@ function renderRepoList(findings, scoreKeys) {
|
|
|
4273
4779
|
return findings.slice().sort((a, b) => scoreFor(b, scoreKeys) - scoreFor(a, scoreKeys) || b.totalScore - a.totalScore || b.stars - a.stars).slice(0, 12).map((finding) => `- ${finding.fullName} (${finding.category}) - focus score ${scoreFor(finding, scoreKeys)}, total ${finding.totalScore}/${maxTotalScore}`).join("\n");
|
|
4274
4780
|
}
|
|
4275
4781
|
function summarizeFindings(cwd) {
|
|
4276
|
-
const findingsDir =
|
|
4277
|
-
if (!
|
|
4782
|
+
const findingsDir = join21(cwd, "research", "findings");
|
|
4783
|
+
if (!existsSync20(findingsDir)) {
|
|
4278
4784
|
throw new Error("No research/findings directory exists. Run agent-kit research scan first.");
|
|
4279
4785
|
}
|
|
4280
4786
|
const findingFiles = readdirSync3(findingsDir).filter((file) => file.endsWith(".md"));
|
|
4281
|
-
const findings = findingFiles.map((file) => parseFinding(file,
|
|
4787
|
+
const findings = findingFiles.map((file) => parseFinding(file, readFileSync18(join21(findingsDir, file), "utf8"))).filter((finding) => finding !== null);
|
|
4282
4788
|
const categoryCounts = countBy(findings.map((finding) => finding.category));
|
|
4283
4789
|
const outputs = [];
|
|
4284
4790
|
const overview = `# Research Scan Overview
|
|
@@ -4297,13 +4803,13 @@ ${countBy(findings.flatMap((finding) => finding.strongPractices)).slice(0, 12).m
|
|
|
4297
4803
|
## Most Repeated Gaps
|
|
4298
4804
|
${countBy(findings.flatMap((finding) => finding.weakPractices)).slice(0, 12).map(([practice, count]) => `- ${practice} (${count})`).join("\n")}
|
|
4299
4805
|
`;
|
|
4300
|
-
const overviewPath =
|
|
4806
|
+
const overviewPath = join21(cwd, "research", "summaries", "scan-overview.md");
|
|
4301
4807
|
writeText(overviewPath, overview);
|
|
4302
4808
|
outputs.push(overviewPath);
|
|
4303
4809
|
for (const [target, config] of Object.entries(SUMMARY_TARGETS)) {
|
|
4304
4810
|
const categories = config.categories;
|
|
4305
4811
|
const scopedFindings = findings.filter((finding) => categories.includes(finding.category));
|
|
4306
|
-
const path =
|
|
4812
|
+
const path = join21(cwd, "research", "summaries", `${target}.md`);
|
|
4307
4813
|
const summary2 = `# ${config.title}
|
|
4308
4814
|
|
|
4309
4815
|
Generated from ${scopedFindings.length} relevant repository findings.
|
|
@@ -4344,7 +4850,7 @@ Review the generated research summaries, then convert repeated best practices in
|
|
|
4344
4850
|
|
|
4345
4851
|
Do not copy source code from scanned repositories. Adopt only generalized practices with clear rationale.
|
|
4346
4852
|
`;
|
|
4347
|
-
const path =
|
|
4853
|
+
const path = join21(cwd, "research", "proposed-updates.md");
|
|
4348
4854
|
writeText(path, output);
|
|
4349
4855
|
return path;
|
|
4350
4856
|
}
|
|
@@ -4461,8 +4967,9 @@ function proposeCorrectionUpstream(cwd, id) {
|
|
|
4461
4967
|
}
|
|
4462
4968
|
|
|
4463
4969
|
// src/studio/session.ts
|
|
4464
|
-
import {
|
|
4465
|
-
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";
|
|
4466
4973
|
function sessionDir(sessionId) {
|
|
4467
4974
|
return `${COUNCIL_SESSIONS_DIR}/${safeSlug(sessionId)}`;
|
|
4468
4975
|
}
|
|
@@ -4478,6 +4985,9 @@ function indexPath(sessionId) {
|
|
|
4478
4985
|
function transcriptPath(sessionId) {
|
|
4479
4986
|
return `${sessionDir(sessionId)}/transcript.md`;
|
|
4480
4987
|
}
|
|
4988
|
+
function sessionLockPath(sessionId) {
|
|
4989
|
+
return `${sessionDir(sessionId)}/.session.lock`;
|
|
4990
|
+
}
|
|
4481
4991
|
function readDefaultWorkflowOutputs(cwd, workflowId) {
|
|
4482
4992
|
const roster = readJsonFile(cwd, DEFAULT_AGENT_ROSTER_TARGET);
|
|
4483
4993
|
const workflow = roster?.workflows?.find((item) => item.id === workflowId);
|
|
@@ -4489,7 +4999,7 @@ function readDefaultWorkflowOutputs(cwd, workflowId) {
|
|
|
4489
4999
|
function startSession(cwd, options) {
|
|
4490
5000
|
ensureStudioDirs(cwd);
|
|
4491
5001
|
const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4492
|
-
const sessionId = safeSlug(`${datePrefix}-${options.title}`);
|
|
5002
|
+
const sessionId = safeSlug(`${datePrefix}-${options.title}-${randomUUID2().slice(0, 8)}`);
|
|
4493
5003
|
const now = nowIso();
|
|
4494
5004
|
const workflowId = options.workflowId ?? "planning";
|
|
4495
5005
|
const session2 = {
|
|
@@ -4519,9 +5029,9 @@ function startSession(cwd, options) {
|
|
|
4519
5029
|
return { sessionId, sessionPath: sessionDir(sessionId) };
|
|
4520
5030
|
}
|
|
4521
5031
|
function listSessions(cwd) {
|
|
4522
|
-
const root =
|
|
4523
|
-
if (!
|
|
4524
|
-
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));
|
|
4525
5035
|
}
|
|
4526
5036
|
function getActiveSessionId(cwd) {
|
|
4527
5037
|
const active = readTextFile(cwd, ACTIVE_SESSION_FILE)?.trim();
|
|
@@ -4547,7 +5057,18 @@ function writeSession(cwd, session2) {
|
|
|
4547
5057
|
writeJsonFile(cwd, sessionJsonPath(session2.sessionId), StudioSessionContract.parse(session2));
|
|
4548
5058
|
}
|
|
4549
5059
|
function appendSessionEvent(cwd, sessionId, event) {
|
|
4550
|
-
|
|
5060
|
+
return withFileLock(cwd, sessionLockPath(sessionId), () => appendSessionEventUnlocked(cwd, sessionId, event));
|
|
5061
|
+
}
|
|
5062
|
+
function appendSessionEventUnlocked(cwd, sessionId, event) {
|
|
5063
|
+
const existingEvents = readSessionEvents(cwd, sessionId);
|
|
5064
|
+
const sequence = existingEvents.reduce((highest, item) => Math.max(highest, item.sequence ?? 0), 0) + 1;
|
|
5065
|
+
const parsed = SessionEventContract.parse(
|
|
5066
|
+
redactEvent({
|
|
5067
|
+
...event,
|
|
5068
|
+
eventId: event.eventId ?? randomUUID2(),
|
|
5069
|
+
sequence: event.sequence ?? sequence
|
|
5070
|
+
})
|
|
5071
|
+
);
|
|
4551
5072
|
appendJsonLine(cwd, eventsPath(sessionId), parsed);
|
|
4552
5073
|
const session2 = readSession(cwd, sessionId);
|
|
4553
5074
|
const updated = {
|
|
@@ -4640,22 +5161,24 @@ function recordRequiredOutput(cwd, name, status, evidence) {
|
|
|
4640
5161
|
const trimmedName = name.trim();
|
|
4641
5162
|
if (!trimmedName) throw new Error("Required output name is required.");
|
|
4642
5163
|
const sessionId = getActiveSessionId(cwd);
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
5164
|
+
return withFileLock(cwd, sessionLockPath(sessionId), () => {
|
|
5165
|
+
const session2 = readSession(cwd, sessionId);
|
|
5166
|
+
const now = nowIso();
|
|
5167
|
+
const output = {
|
|
5168
|
+
name: trimmedName,
|
|
5169
|
+
status,
|
|
5170
|
+
...evidence ? { evidence: redactSensitive(evidence) } : {}
|
|
5171
|
+
};
|
|
5172
|
+
const outputIndex = session2.requiredOutputs.findIndex((item) => item.name === trimmedName);
|
|
5173
|
+
const requiredOutputs = outputIndex === -1 ? [...session2.requiredOutputs, output] : session2.requiredOutputs.map((item, index) => index === outputIndex ? { ...item, ...output } : item);
|
|
5174
|
+
writeSession(cwd, { ...session2, requiredOutputs, updatedAt: now });
|
|
5175
|
+
return appendSessionEventUnlocked(cwd, sessionId, {
|
|
5176
|
+
type: "required_output_updated",
|
|
5177
|
+
createdAt: now,
|
|
5178
|
+
outputName: trimmedName,
|
|
5179
|
+
outputStatus: status,
|
|
5180
|
+
...evidence ? { evidence: [evidence] } : {}
|
|
5181
|
+
});
|
|
4659
5182
|
});
|
|
4660
5183
|
}
|
|
4661
5184
|
function closeSession(cwd, status) {
|
|
@@ -4672,14 +5195,16 @@ function renderActiveSession(cwd) {
|
|
|
4672
5195
|
return renderSession(cwd, getActiveSessionId(cwd));
|
|
4673
5196
|
}
|
|
4674
5197
|
function renderSession(cwd, sessionId) {
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
5198
|
+
return withFileLock(cwd, sessionLockPath(sessionId), () => {
|
|
5199
|
+
const session2 = readSession(cwd, sessionId);
|
|
5200
|
+
const events = readSessionEvents(cwd, sessionId);
|
|
5201
|
+
const renderedAt = nowIso();
|
|
5202
|
+
const updated = { ...session2, renderedAt, updatedAt: renderedAt };
|
|
5203
|
+
writeTextFile(cwd, indexPath(sessionId), renderSessionIndex(updated, events));
|
|
5204
|
+
writeTextFile(cwd, transcriptPath(sessionId), renderSessionTranscript(updated, events));
|
|
5205
|
+
writeSession(cwd, updated);
|
|
5206
|
+
return { sessionId, sessionPath: sessionDir(sessionId) };
|
|
5207
|
+
});
|
|
4683
5208
|
}
|
|
4684
5209
|
function renderSessionIndex(session2, events) {
|
|
4685
5210
|
const handoffs = events.filter((event) => event.type === "handoff");
|
|
@@ -4738,7 +5263,7 @@ ${verification.map((event) => `| ${escapeMarkdownTableCell(event.command)} | ${e
|
|
|
4738
5263
|
## Next Actions
|
|
4739
5264
|
|
|
4740
5265
|
${renderNextActions(session2, verification)}
|
|
4741
|
-
|
|
5266
|
+
`.trimEnd() + "\n";
|
|
4742
5267
|
}
|
|
4743
5268
|
function renderDecisionRow(event) {
|
|
4744
5269
|
if (event.type === "handoff") {
|
|
@@ -4793,7 +5318,7 @@ ${rows.join("\n")}`;
|
|
|
4793
5318
|
Generated from \`${sessionDir(session2.sessionId)}/events.jsonl\`.
|
|
4794
5319
|
|
|
4795
5320
|
${sections || "No events recorded."}
|
|
4796
|
-
|
|
5321
|
+
`.trimEnd() + "\n";
|
|
4797
5322
|
}
|
|
4798
5323
|
|
|
4799
5324
|
// src/studio/export.ts
|
|
@@ -5056,19 +5581,19 @@ function safeJsonForHtml(value) {
|
|
|
5056
5581
|
}
|
|
5057
5582
|
|
|
5058
5583
|
// src/studio/setup-browser.ts
|
|
5059
|
-
import { execFileSync } from "child_process";
|
|
5584
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
5060
5585
|
function openBrowser(url) {
|
|
5061
5586
|
const platform = process.platform;
|
|
5062
5587
|
try {
|
|
5063
5588
|
if (platform === "darwin") {
|
|
5064
|
-
|
|
5589
|
+
execFileSync2("open", [url], { stdio: "ignore" });
|
|
5065
5590
|
return;
|
|
5066
5591
|
}
|
|
5067
5592
|
if (platform === "win32") {
|
|
5068
|
-
|
|
5593
|
+
execFileSync2("cmd", ["/c", "start", "", url], { stdio: "ignore" });
|
|
5069
5594
|
return;
|
|
5070
5595
|
}
|
|
5071
|
-
|
|
5596
|
+
execFileSync2("xdg-open", [url], { stdio: "ignore" });
|
|
5072
5597
|
} catch {
|
|
5073
5598
|
console.log(`Open this URL in your browser: ${url}`);
|
|
5074
5599
|
}
|
|
@@ -5080,10 +5605,10 @@ async function promptStartSetup(defaultYes = true) {
|
|
|
5080
5605
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
5081
5606
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5082
5607
|
const question = defaultYes ? "Start setup wizard now? [Y/n] " : "Start setup wizard now? [y/N] ";
|
|
5083
|
-
const answer = await new Promise((
|
|
5608
|
+
const answer = await new Promise((resolve4) => {
|
|
5084
5609
|
rl.question(question, (value) => {
|
|
5085
5610
|
rl.close();
|
|
5086
|
-
|
|
5611
|
+
resolve4(value.trim().toLowerCase());
|
|
5087
5612
|
});
|
|
5088
5613
|
});
|
|
5089
5614
|
if (!answer) return defaultYes;
|
|
@@ -5202,8 +5727,8 @@ function parseSetupFormPayload(raw) {
|
|
|
5202
5727
|
}
|
|
5203
5728
|
|
|
5204
5729
|
// src/studio/wizard/checklist.ts
|
|
5205
|
-
import { existsSync as
|
|
5206
|
-
import { join as
|
|
5730
|
+
import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
|
|
5731
|
+
import { join as join23 } from "path";
|
|
5207
5732
|
var IDE_PATHS = {
|
|
5208
5733
|
cursor: ".cursor/agents/planner.md",
|
|
5209
5734
|
copilot: ".github/copilot-instructions.md",
|
|
@@ -5224,12 +5749,12 @@ function saveIdeChecklist(cwd, ideSurface) {
|
|
|
5224
5749
|
function detectIdeRulePresent(cwd, ideSurface) {
|
|
5225
5750
|
const rel = IDE_PATHS[ideSurface];
|
|
5226
5751
|
if (ideSurface === "cursor") {
|
|
5227
|
-
return
|
|
5752
|
+
return existsSync22(join23(cwd, rel)) || existsSync22(join23(cwd, ".cursor/rules/cursor-agent-kit.mdc"));
|
|
5228
5753
|
}
|
|
5229
5754
|
if (rel.endsWith("/")) {
|
|
5230
|
-
return
|
|
5755
|
+
return existsSync22(join23(cwd, rel));
|
|
5231
5756
|
}
|
|
5232
|
-
return
|
|
5757
|
+
return existsSync22(join23(cwd, rel));
|
|
5233
5758
|
}
|
|
5234
5759
|
var VISUAL_QA_MARKER = "## Visual QA Tier";
|
|
5235
5760
|
var VISUAL_QA_BLOCKS = {
|
|
@@ -5257,11 +5782,11 @@ This project uses the **Mature** visual QA tier.
|
|
|
5257
5782
|
};
|
|
5258
5783
|
function writeVisualQaTier(cwd, tier) {
|
|
5259
5784
|
const path = "TESTING.md";
|
|
5260
|
-
const fullPath =
|
|
5261
|
-
if (!
|
|
5785
|
+
const fullPath = join23(cwd, path);
|
|
5786
|
+
if (!existsSync22(fullPath)) {
|
|
5262
5787
|
return { updated: false, path, reason: "TESTING.md not found in project root." };
|
|
5263
5788
|
}
|
|
5264
|
-
const current =
|
|
5789
|
+
const current = readFileSync20(fullPath, "utf8");
|
|
5265
5790
|
if (current.includes(VISUAL_QA_MARKER)) {
|
|
5266
5791
|
return {
|
|
5267
5792
|
updated: false,
|
|
@@ -5327,7 +5852,7 @@ function renderAgentBriefsMarkdown(cwd, file) {
|
|
|
5327
5852
|
const text = file.briefs[agent.id]?.trim();
|
|
5328
5853
|
if (!text) continue;
|
|
5329
5854
|
wroteAny = true;
|
|
5330
|
-
lines.push(`## ${agent.name}`, "", `**Role:** ${agent.roleSummary}`, "", text, "");
|
|
5855
|
+
lines.push(`## ${escapeMarkdownText(agent.name)}`, "", `**Role:** ${escapeMarkdownText(agent.roleSummary)}`, "", escapeMarkdownText(text), "");
|
|
5331
5856
|
}
|
|
5332
5857
|
if (!wroteAny) {
|
|
5333
5858
|
lines.push("_No agent briefs recorded yet. Run `agent-kit setup` to brief your team._", "");
|
|
@@ -5437,8 +5962,8 @@ function extractSetupFormFromWizardForm(form) {
|
|
|
5437
5962
|
}
|
|
5438
5963
|
|
|
5439
5964
|
// src/studio/wizard/drafts.ts
|
|
5440
|
-
import { existsSync as
|
|
5441
|
-
import { join as
|
|
5965
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
|
|
5966
|
+
import { join as join24 } from "path";
|
|
5442
5967
|
var DESIGN_DRAFT_JSON = ".agent-kit/onboarding/design-draft.json";
|
|
5443
5968
|
var MESSAGING_DRAFT_JSON = ".agent-kit/onboarding/messaging-draft.json";
|
|
5444
5969
|
function loadDesignDraft(cwd) {
|
|
@@ -5464,12 +5989,12 @@ function previewDesignMarkdown(draft) {
|
|
|
5464
5989
|
|
|
5465
5990
|
| Area | Wizard draft |
|
|
5466
5991
|
| --- | --- |
|
|
5467
|
-
| Primary audience | ${draft.audience.trim() || "TBD"} |
|
|
5468
|
-
| Content inventory | ${draft.contentInventory.trim() || "TBD"} |
|
|
5992
|
+
| Primary audience | ${escapeMarkdownTableCell(draft.audience.trim() || "TBD")} |
|
|
5993
|
+
| Content inventory | ${escapeMarkdownTableCell(draft.contentInventory.trim() || "TBD")} |
|
|
5469
5994
|
|
|
5470
5995
|
## Anti-References (wizard draft)
|
|
5471
5996
|
|
|
5472
|
-
${draft.antiReferences.trim() || "- TBD: pattern to avoid."}
|
|
5997
|
+
${escapeMarkdownText(draft.antiReferences.trim() || "- TBD: pattern to avoid.")}
|
|
5473
5998
|
`;
|
|
5474
5999
|
}
|
|
5475
6000
|
function previewMessagingMarkdown(draft) {
|
|
@@ -5477,24 +6002,29 @@ function previewMessagingMarkdown(draft) {
|
|
|
5477
6002
|
|
|
5478
6003
|
| Question | Current Answer |
|
|
5479
6004
|
| --- | --- |
|
|
5480
|
-
| Who is the primary audience? | ${draft.audience.trim() || "TBD"} |
|
|
5481
|
-
| What painful problem do they need solved? | ${draft.pain.trim() || "TBD"} |
|
|
5482
|
-
| What outcome do they want? | ${draft.outcome.trim() || "TBD"} |
|
|
6005
|
+
| Who is the primary audience? | ${escapeMarkdownTableCell(draft.audience.trim() || "TBD")} |
|
|
6006
|
+
| What painful problem do they need solved? | ${escapeMarkdownTableCell(draft.pain.trim() || "TBD")} |
|
|
6007
|
+
| What outcome do they want? | ${escapeMarkdownTableCell(draft.outcome.trim() || "TBD")} |
|
|
5483
6008
|
`;
|
|
5484
6009
|
}
|
|
5485
6010
|
function appendSectionToDoc(cwd, doc, sectionMarkdown) {
|
|
5486
|
-
const fullPath =
|
|
5487
|
-
if (!
|
|
6011
|
+
const fullPath = join24(cwd, doc);
|
|
6012
|
+
if (!existsSync23(fullPath)) {
|
|
5488
6013
|
return { target: doc, action: "missing" };
|
|
5489
6014
|
}
|
|
5490
|
-
const current =
|
|
5491
|
-
|
|
5492
|
-
return { target: doc, action: "conflict", conflictPath: `.agent-kit/conflicts/wizard-${doc}` };
|
|
5493
|
-
}
|
|
5494
|
-
writeTextFile(cwd, doc, `${current.trimEnd()}
|
|
6015
|
+
const current = readFileSync21(fullPath, "utf8");
|
|
6016
|
+
const proposed = `${current.trimEnd()}
|
|
5495
6017
|
|
|
5496
6018
|
${sectionMarkdown.trim()}
|
|
5497
|
-
|
|
6019
|
+
`;
|
|
6020
|
+
if (current.includes("(wizard draft)")) {
|
|
6021
|
+
const conflict = writeConflictProposal(cwd, doc, proposed, {
|
|
6022
|
+
currentContent: current,
|
|
6023
|
+
reason: "The target already contains an applied wizard draft."
|
|
6024
|
+
});
|
|
6025
|
+
return { target: doc, action: "conflict", conflictPath: conflict.conflictPath };
|
|
6026
|
+
}
|
|
6027
|
+
writeTextFile(cwd, doc, proposed);
|
|
5498
6028
|
return { target: doc, action: "appended" };
|
|
5499
6029
|
}
|
|
5500
6030
|
function applyDesignDraft(cwd) {
|
|
@@ -5526,8 +6056,8 @@ function applyDrafts(cwd) {
|
|
|
5526
6056
|
}
|
|
5527
6057
|
|
|
5528
6058
|
// src/studio/office/render.ts
|
|
5529
|
-
import { readFileSync as
|
|
5530
|
-
import { join as
|
|
6059
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
6060
|
+
import { join as join25 } from "path";
|
|
5531
6061
|
|
|
5532
6062
|
// src/studio/office/map.ts
|
|
5533
6063
|
var MAP_WIDTH = 28;
|
|
@@ -5649,12 +6179,12 @@ var PRODUCT_CATEGORIES = ["content-app", "saas", "admin", "marketplace", "tool",
|
|
|
5649
6179
|
var TENANT_MODELS = ["single-user", "team", "tenant", "marketplace", "admin", "public-content"];
|
|
5650
6180
|
function readOfficeAsset(name) {
|
|
5651
6181
|
const root = findPackageRoot();
|
|
5652
|
-
const distPath =
|
|
5653
|
-
const srcPath =
|
|
6182
|
+
const distPath = join25(root, "dist", "studio", "office", "assets", name);
|
|
6183
|
+
const srcPath = join25(root, "src", "studio", "office", "assets", name);
|
|
5654
6184
|
try {
|
|
5655
|
-
return
|
|
6185
|
+
return readFileSync22(distPath, "utf8");
|
|
5656
6186
|
} catch {
|
|
5657
|
-
return
|
|
6187
|
+
return readFileSync22(srcPath, "utf8");
|
|
5658
6188
|
}
|
|
5659
6189
|
}
|
|
5660
6190
|
function buildOfficeBootConfig(cwd, viewModel) {
|
|
@@ -5699,6 +6229,51 @@ function renderOfficeHtml(boot, mode) {
|
|
|
5699
6229
|
const isStudio = mode === "studio";
|
|
5700
6230
|
const title = isStudio ? "Agent Kit \u2014 Live Studio" : "Agent Kit \u2014 Setup Office";
|
|
5701
6231
|
const dataView = isStudio ? "studio-v1" : "office-v1";
|
|
6232
|
+
const studioAside = `<aside class="transcript-panel" id="transcript-panel" aria-label="Agent Studio activity">
|
|
6233
|
+
<div class="studio-tabs" role="tablist" aria-label="Studio view">
|
|
6234
|
+
<button type="button" class="studio-tab active" id="studio-council-tab" role="tab" aria-selected="true" aria-controls="studio-council-view">Council</button>
|
|
6235
|
+
<button type="button" class="studio-tab" id="studio-runtime-tab" role="tab" aria-selected="false" aria-controls="studio-runtime-view">Runs</button>
|
|
6236
|
+
</div>
|
|
6237
|
+
<section id="studio-council-view" role="tabpanel" aria-labelledby="studio-council-tab">
|
|
6238
|
+
<div class="studio-controls" id="studio-controls">
|
|
6239
|
+
<label class="studio-label" for="session-picker">Session</label>
|
|
6240
|
+
<select id="session-picker" aria-label="Council session"></select>
|
|
6241
|
+
<form id="studio-note-form" class="studio-note-form">
|
|
6242
|
+
<select id="studio-note-agent" aria-label="Agent for note"></select>
|
|
6243
|
+
<input id="studio-note-text" type="text" maxlength="3999" placeholder="Add council note\u2026" />
|
|
6244
|
+
<button type="submit" class="btn secondary">Add note</button>
|
|
6245
|
+
</form>
|
|
6246
|
+
<button type="button" class="btn primary" id="studio-render-btn">Render markdown</button>
|
|
6247
|
+
</div>
|
|
6248
|
+
<h2>Transcript</h2>
|
|
6249
|
+
<ol id="transcript-list"></ol>
|
|
6250
|
+
</section>
|
|
6251
|
+
<section id="studio-runtime-view" role="tabpanel" aria-labelledby="studio-runtime-tab" hidden>
|
|
6252
|
+
<form id="runtime-start-form" class="runtime-start-form">
|
|
6253
|
+
<label class="studio-label" for="runtime-goal">Goal</label>
|
|
6254
|
+
<textarea id="runtime-goal" maxlength="20000" rows="4" required></textarea>
|
|
6255
|
+
<label class="runtime-check"><input id="runtime-dirty-base" type="checkbox" /> Exclude current local changes</label>
|
|
6256
|
+
<button type="submit" class="btn primary" id="runtime-start-btn" disabled>Start run</button>
|
|
6257
|
+
</form>
|
|
6258
|
+
<div class="runtime-toolbar">
|
|
6259
|
+
<label class="studio-label" for="runtime-picker">Run</label>
|
|
6260
|
+
<select id="runtime-picker" aria-label="Runtime run"></select>
|
|
6261
|
+
<button type="button" class="btn secondary" id="runtime-refresh-btn">Refresh</button>
|
|
6262
|
+
</div>
|
|
6263
|
+
<dl class="runtime-summary" id="runtime-summary"></dl>
|
|
6264
|
+
<div class="runtime-approval" id="runtime-approval" hidden>
|
|
6265
|
+
<strong id="runtime-approval-title"></strong>
|
|
6266
|
+
<p id="runtime-approval-detail"></p>
|
|
6267
|
+
<div class="runtime-actions">
|
|
6268
|
+
<button type="button" class="btn primary" id="runtime-approve-btn">Approve</button>
|
|
6269
|
+
<button type="button" class="btn secondary" id="runtime-reject-btn">Reject</button>
|
|
6270
|
+
</div>
|
|
6271
|
+
</div>
|
|
6272
|
+
<button type="button" class="btn secondary runtime-cancel" id="runtime-cancel-btn" hidden>Cancel run</button>
|
|
6273
|
+
<h2>Run events</h2>
|
|
6274
|
+
<ol id="runtime-event-list"></ol>
|
|
6275
|
+
</section>
|
|
6276
|
+
</aside>`;
|
|
5702
6277
|
return `<!doctype html>
|
|
5703
6278
|
<html lang="en">
|
|
5704
6279
|
<head>
|
|
@@ -5740,7 +6315,7 @@ function renderOfficeHtml(boot, mode) {
|
|
|
5740
6315
|
<div id="nameplate-layer" class="nameplate-layer" aria-hidden="true"></div>
|
|
5741
6316
|
<div id="office-hint" class="office-hint hidden" role="status">${isStudio ? "Watching council session events\u2026" : "Click a desk or zone to brief your agent team."}</div>
|
|
5742
6317
|
</div>
|
|
5743
|
-
${isStudio ?
|
|
6318
|
+
${isStudio ? studioAside : ""}
|
|
5744
6319
|
</main>
|
|
5745
6320
|
<div id="status" class="status" role="status" aria-live="polite"></div>
|
|
5746
6321
|
<div id="depth-modal" class="modal modal-blur" hidden>
|
|
@@ -5826,18 +6401,18 @@ function allAgentBriefsComplete(form, agentIds) {
|
|
|
5826
6401
|
}
|
|
5827
6402
|
|
|
5828
6403
|
// src/studio/wizard/render.ts
|
|
5829
|
-
import { readFileSync as
|
|
5830
|
-
import { join as
|
|
6404
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
6405
|
+
import { join as join26 } from "path";
|
|
5831
6406
|
var PRODUCT_CATEGORIES2 = ["content-app", "saas", "admin", "marketplace", "tool", "ecommerce", "portfolio", "education", "community", "ai-workflow", "other"];
|
|
5832
6407
|
var TENANT_MODELS2 = ["single-user", "team", "tenant", "marketplace", "admin", "public-content"];
|
|
5833
6408
|
function readWizardAsset(name) {
|
|
5834
6409
|
const root = findPackageRoot();
|
|
5835
|
-
const distPath =
|
|
5836
|
-
const srcPath =
|
|
6410
|
+
const distPath = join26(root, "dist", "studio", "wizard", "assets", name);
|
|
6411
|
+
const srcPath = join26(root, "src", "studio", "wizard", "assets", name);
|
|
5837
6412
|
try {
|
|
5838
|
-
return
|
|
6413
|
+
return readFileSync23(distPath, "utf8");
|
|
5839
6414
|
} catch {
|
|
5840
|
-
return
|
|
6415
|
+
return readFileSync23(srcPath, "utf8");
|
|
5841
6416
|
}
|
|
5842
6417
|
}
|
|
5843
6418
|
function mergeWizardSteps(cwd) {
|
|
@@ -5927,12 +6502,12 @@ function renderSetupWizardHtmlWithContext(cwd) {
|
|
|
5927
6502
|
}
|
|
5928
6503
|
|
|
5929
6504
|
// src/studio/agentic-level.ts
|
|
5930
|
-
import { existsSync as
|
|
5931
|
-
import { join as
|
|
6505
|
+
import { existsSync as existsSync24, readFileSync as readFileSync24 } from "fs";
|
|
6506
|
+
import { join as join27 } from "path";
|
|
5932
6507
|
var CACHE_TTL_MS = 3e4;
|
|
5933
6508
|
var cache = /* @__PURE__ */ new Map();
|
|
5934
6509
|
function isMaintainerSourceRepo(cwd) {
|
|
5935
|
-
return
|
|
6510
|
+
return existsSync24(join27(cwd, "package.json")) && existsSync24(join27(cwd, "src")) && existsSync24(join27(cwd, "templates"));
|
|
5936
6511
|
}
|
|
5937
6512
|
function signal(id, level, label, pass, evidence, remediation) {
|
|
5938
6513
|
return { id, level, label, pass, evidence, remediation };
|
|
@@ -5948,23 +6523,23 @@ function detectIdePresent(cwd) {
|
|
|
5948
6523
|
return { pass: true, evidence: `${surface} adapter files detected` };
|
|
5949
6524
|
}
|
|
5950
6525
|
}
|
|
5951
|
-
if (
|
|
6526
|
+
if (existsSync24(join27(cwd, ".cursor/rules/cursor-agent-kit.mdc"))) {
|
|
5952
6527
|
return { pass: true, evidence: "Cursor council rules from init" };
|
|
5953
6528
|
}
|
|
5954
6529
|
return { pass: false, evidence: "No IDE adapter rules or subagents detected" };
|
|
5955
6530
|
}
|
|
5956
6531
|
function detectTierBSubagents(cwd) {
|
|
5957
6532
|
const paths = [".cursor/agents/planner.md", ".codex/agents/planner.toml", ".claude/agents/planner.md", ".github/copilot-instructions.md"];
|
|
5958
|
-
const found = paths.filter((rel) =>
|
|
6533
|
+
const found = paths.filter((rel) => existsSync24(join27(cwd, rel)));
|
|
5959
6534
|
if (found.length > 0) {
|
|
5960
6535
|
return { pass: true, evidence: `Specialist surface: ${found[0]}` };
|
|
5961
6536
|
}
|
|
5962
6537
|
return { pass: false, evidence: "No council subagents or Copilot instructions installed" };
|
|
5963
6538
|
}
|
|
5964
6539
|
function readDocSnippet(cwd, name, needles) {
|
|
5965
|
-
const path =
|
|
5966
|
-
if (!
|
|
5967
|
-
const lower =
|
|
6540
|
+
const path = join27(cwd, name);
|
|
6541
|
+
if (!existsSync24(path)) return false;
|
|
6542
|
+
const lower = readFileSync24(path, "utf8").toLowerCase();
|
|
5968
6543
|
return needles.every((needle) => lower.includes(needle.toLowerCase()));
|
|
5969
6544
|
}
|
|
5970
6545
|
function adapterTargetForIde(ide) {
|
|
@@ -5992,8 +6567,8 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
5992
6567
|
"l4-agents-md",
|
|
5993
6568
|
4,
|
|
5994
6569
|
"Council contract (AGENTS.md)",
|
|
5995
|
-
|
|
5996
|
-
|
|
6570
|
+
existsSync24(join27(cwd, "AGENTS.md")),
|
|
6571
|
+
existsSync24(join27(cwd, "AGENTS.md")) ? "AGENTS.md installed" : "AGENTS.md missing",
|
|
5997
6572
|
"Run agent-kit init --stack next-supabase"
|
|
5998
6573
|
)
|
|
5999
6574
|
);
|
|
@@ -6002,8 +6577,8 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
6002
6577
|
"l4-adapters-doc",
|
|
6003
6578
|
4,
|
|
6004
6579
|
"Assistant activation doc",
|
|
6005
|
-
|
|
6006
|
-
|
|
6580
|
+
existsSync24(join27(cwd, "ASSISTANT_ADAPTERS.md")),
|
|
6581
|
+
existsSync24(join27(cwd, "ASSISTANT_ADAPTERS.md")) ? "ASSISTANT_ADAPTERS.md installed" : "ASSISTANT_ADAPTERS.md missing",
|
|
6007
6582
|
"Run agent-kit init or agent-kit update"
|
|
6008
6583
|
)
|
|
6009
6584
|
);
|
|
@@ -6012,8 +6587,8 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
6012
6587
|
"l4-roster",
|
|
6013
6588
|
4,
|
|
6014
6589
|
"Machine-readable council roster",
|
|
6015
|
-
|
|
6016
|
-
|
|
6590
|
+
existsSync24(join27(cwd, ".agent-kit/agent-roster.json")),
|
|
6591
|
+
existsSync24(join27(cwd, ".agent-kit/agent-roster.json")) ? ".agent-kit/agent-roster.json present" : "Roster missing",
|
|
6017
6592
|
"Run agent-kit init or agent-kit update"
|
|
6018
6593
|
)
|
|
6019
6594
|
);
|
|
@@ -6031,7 +6606,7 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
6031
6606
|
signals.push(
|
|
6032
6607
|
signal("l5-subagents", 5, "Tier-B specialist activation", tierB.pass, tierB.evidence, "Run agent-kit init --activate cursor|codex|claude|copilot")
|
|
6033
6608
|
);
|
|
6034
|
-
const loopCoding =
|
|
6609
|
+
const loopCoding = existsSync24(join27(cwd, "LOOP_CODING.md"));
|
|
6035
6610
|
signals.push(
|
|
6036
6611
|
signal(
|
|
6037
6612
|
"l6-loop-coding",
|
|
@@ -6062,12 +6637,12 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
6062
6637
|
)
|
|
6063
6638
|
);
|
|
6064
6639
|
if (maintainerProfile) {
|
|
6065
|
-
const pkgPath =
|
|
6640
|
+
const pkgPath = join27(cwd, "package.json");
|
|
6066
6641
|
let releaseCheck = false;
|
|
6067
|
-
if (
|
|
6642
|
+
if (existsSync24(pkgPath)) {
|
|
6068
6643
|
try {
|
|
6069
|
-
const pkg = JSON.parse(
|
|
6070
|
-
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"));
|
|
6071
6646
|
} catch {
|
|
6072
6647
|
releaseCheck = false;
|
|
6073
6648
|
}
|
|
@@ -6082,7 +6657,7 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
6082
6657
|
"Use npm run release:check before merge; see MAINTAINER_RELEASE.md"
|
|
6083
6658
|
)
|
|
6084
6659
|
);
|
|
6085
|
-
const maintainerDocs =
|
|
6660
|
+
const maintainerDocs = existsSync24(join27(cwd, "MAINTAINER_RELEASE.md")) || readDocSnippet(cwd, "DOCS.md", ["maintainer dogfood", "dogfood:init"]);
|
|
6086
6661
|
signals.push(
|
|
6087
6662
|
signal(
|
|
6088
6663
|
"l6-maintainer-docs",
|
|
@@ -6110,8 +6685,8 @@ function buildSignals(cwd, maintainerProfile) {
|
|
|
6110
6685
|
signals.push(
|
|
6111
6686
|
signal("l6-adapter-validate", 6, "Adapter validate for active IDE", adapterPass, adapterEvidence, "Run agent-kit adapter validate cursor|codex|all")
|
|
6112
6687
|
);
|
|
6113
|
-
const ciWorkflow =
|
|
6114
|
-
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"]);
|
|
6115
6690
|
const evalLoop = ciWorkflow || testingEval;
|
|
6116
6691
|
signals.push(
|
|
6117
6692
|
signal(
|
|
@@ -6192,23 +6767,138 @@ function invalidateAgenticLevelCache(cwd) {
|
|
|
6192
6767
|
cache.delete(cwd);
|
|
6193
6768
|
}
|
|
6194
6769
|
|
|
6770
|
+
// src/studio/local-http-security.ts
|
|
6771
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
6772
|
+
var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
6773
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost"]);
|
|
6774
|
+
var LocalHttpRequestError = class extends Error {
|
|
6775
|
+
statusCode;
|
|
6776
|
+
constructor(statusCode, message) {
|
|
6777
|
+
super(message);
|
|
6778
|
+
this.name = "LocalHttpRequestError";
|
|
6779
|
+
this.statusCode = statusCode;
|
|
6780
|
+
}
|
|
6781
|
+
};
|
|
6782
|
+
function isLoopbackHost(host) {
|
|
6783
|
+
return LOOPBACK_HOSTS.has(
|
|
6784
|
+
host.trim().toLowerCase().replace(/^\[|\]$/g, "")
|
|
6785
|
+
);
|
|
6786
|
+
}
|
|
6787
|
+
function createLocalHttpSecurity(host) {
|
|
6788
|
+
if (!isLoopbackHost(host)) {
|
|
6789
|
+
throw new Error(`Agent Studio only accepts loopback hosts (127.0.0.1, ::1, or localhost); received ${host}.`);
|
|
6790
|
+
}
|
|
6791
|
+
return {
|
|
6792
|
+
csrfToken: randomBytes(32).toString("base64url"),
|
|
6793
|
+
host,
|
|
6794
|
+
port: 0
|
|
6795
|
+
};
|
|
6796
|
+
}
|
|
6797
|
+
function formatLocalUrl(host, port) {
|
|
6798
|
+
const normalized = host.trim().replace(/^\[|\]$/g, "");
|
|
6799
|
+
return `http://${normalized.includes(":") ? `[${normalized}]` : normalized}:${port}`;
|
|
6800
|
+
}
|
|
6801
|
+
function parseAuthority(authority) {
|
|
6802
|
+
try {
|
|
6803
|
+
return new URL(`http://${authority}`);
|
|
6804
|
+
} catch {
|
|
6805
|
+
throw new LocalHttpRequestError(400, "Invalid Host header.");
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
function safeTokenEqual(actual, expected) {
|
|
6809
|
+
const actualBuffer = Buffer.from(actual);
|
|
6810
|
+
const expectedBuffer = Buffer.from(expected);
|
|
6811
|
+
return actualBuffer.length === expectedBuffer.length && timingSafeEqual(actualBuffer, expectedBuffer);
|
|
6812
|
+
}
|
|
6813
|
+
function assertSecureLocalRequest(request, security) {
|
|
6814
|
+
const hostHeader = request.headers.host;
|
|
6815
|
+
if (!hostHeader) throw new LocalHttpRequestError(400, "Host header is required.");
|
|
6816
|
+
const authority = parseAuthority(hostHeader);
|
|
6817
|
+
if (!isLoopbackHost(authority.hostname)) {
|
|
6818
|
+
throw new LocalHttpRequestError(403, "Host must resolve to a loopback address.");
|
|
6819
|
+
}
|
|
6820
|
+
const requestedPort = authority.port ? Number(authority.port) : 80;
|
|
6821
|
+
if (security.port > 0 && requestedPort !== security.port) {
|
|
6822
|
+
throw new LocalHttpRequestError(403, "Host port does not match the local server.");
|
|
6823
|
+
}
|
|
6824
|
+
const method = (request.method ?? "GET").toUpperCase();
|
|
6825
|
+
if (!MUTATING_METHODS.has(method)) return;
|
|
6826
|
+
const contentType = request.headers["content-type"] ?? "";
|
|
6827
|
+
if (!contentType.toLowerCase().startsWith("application/json")) {
|
|
6828
|
+
throw new LocalHttpRequestError(415, "Mutating requests require Content-Type: application/json.");
|
|
6829
|
+
}
|
|
6830
|
+
const token = request.headers["x-agent-kit-csrf"];
|
|
6831
|
+
if (typeof token !== "string" || !safeTokenEqual(token, security.csrfToken)) {
|
|
6832
|
+
throw new LocalHttpRequestError(403, "Missing or invalid local request token.");
|
|
6833
|
+
}
|
|
6834
|
+
const fetchSite = request.headers["sec-fetch-site"];
|
|
6835
|
+
if (typeof fetchSite === "string" && fetchSite !== "same-origin" && fetchSite !== "none") {
|
|
6836
|
+
throw new LocalHttpRequestError(403, "Cross-site requests are not allowed.");
|
|
6837
|
+
}
|
|
6838
|
+
const origin = request.headers.origin;
|
|
6839
|
+
if (typeof origin === "string") {
|
|
6840
|
+
let parsedOrigin;
|
|
6841
|
+
try {
|
|
6842
|
+
parsedOrigin = new URL(origin);
|
|
6843
|
+
} catch {
|
|
6844
|
+
throw new LocalHttpRequestError(403, "Invalid Origin header.");
|
|
6845
|
+
}
|
|
6846
|
+
const originPort = parsedOrigin.port ? Number(parsedOrigin.port) : parsedOrigin.protocol === "https:" ? 443 : 80;
|
|
6847
|
+
if (parsedOrigin.protocol !== "http:" || !isLoopbackHost(parsedOrigin.hostname) || originPort !== security.port) {
|
|
6848
|
+
throw new LocalHttpRequestError(403, "Origin does not match the local server.");
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
}
|
|
6852
|
+
function baseSecurityHeaders() {
|
|
6853
|
+
return {
|
|
6854
|
+
"Cache-Control": "no-store",
|
|
6855
|
+
"Cross-Origin-Opener-Policy": "same-origin",
|
|
6856
|
+
"Cross-Origin-Resource-Policy": "same-origin",
|
|
6857
|
+
"Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=()",
|
|
6858
|
+
"Referrer-Policy": "no-referrer",
|
|
6859
|
+
"X-Content-Type-Options": "nosniff",
|
|
6860
|
+
"X-Frame-Options": "DENY"
|
|
6861
|
+
};
|
|
6862
|
+
}
|
|
6863
|
+
function secureLocalHtml(html, security) {
|
|
6864
|
+
const nonce = randomBytes(18).toString("base64url");
|
|
6865
|
+
const meta = `<meta name="agent-kit-csrf-token" content="${security.csrfToken}">`;
|
|
6866
|
+
const body = html.replace("</head>", ` ${meta}
|
|
6867
|
+
</head>`).replace(/<style>/g, `<style nonce="${nonce}">`).replace(/<script>/g, `<script nonce="${nonce}">`);
|
|
6868
|
+
const csp = [
|
|
6869
|
+
"default-src 'none'",
|
|
6870
|
+
"base-uri 'none'",
|
|
6871
|
+
"connect-src 'self'",
|
|
6872
|
+
"form-action 'self'",
|
|
6873
|
+
"frame-ancestors 'none'",
|
|
6874
|
+
"img-src data:",
|
|
6875
|
+
"object-src 'none'",
|
|
6876
|
+
`script-src 'nonce-${nonce}'`,
|
|
6877
|
+
`style-src 'nonce-${nonce}'`,
|
|
6878
|
+
"style-src-attr 'unsafe-inline'"
|
|
6879
|
+
].join("; ");
|
|
6880
|
+
return { body, csp };
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6195
6883
|
// src/studio/setup-server.ts
|
|
6196
6884
|
var DEFAULT_PORT = 9321;
|
|
6197
6885
|
var DEFAULT_HOST = "127.0.0.1";
|
|
6198
6886
|
function sendJson(response, statusCode, payload) {
|
|
6199
6887
|
response.writeHead(statusCode, {
|
|
6888
|
+
...baseSecurityHeaders(),
|
|
6200
6889
|
"Content-Type": "application/json; charset=utf-8",
|
|
6201
|
-
"
|
|
6890
|
+
"Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'"
|
|
6202
6891
|
});
|
|
6203
6892
|
response.end(JSON.stringify(payload));
|
|
6204
6893
|
}
|
|
6205
|
-
function sendHtml(response, html) {
|
|
6894
|
+
function sendHtml(response, html, security) {
|
|
6895
|
+
const secured = secureLocalHtml(html, security);
|
|
6206
6896
|
response.writeHead(200, {
|
|
6897
|
+
...baseSecurityHeaders(),
|
|
6207
6898
|
"Content-Type": "text/html; charset=utf-8",
|
|
6208
|
-
"
|
|
6209
|
-
"Content-Security-Policy": "default-src 'none'; connect-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'self'"
|
|
6899
|
+
"Content-Security-Policy": secured.csp
|
|
6210
6900
|
});
|
|
6211
|
-
response.end(
|
|
6901
|
+
response.end(secured.body);
|
|
6212
6902
|
}
|
|
6213
6903
|
function buildStatePayload(cwd, options = {}) {
|
|
6214
6904
|
ensureProjectContextForSetup(cwd);
|
|
@@ -6237,7 +6927,7 @@ function buildStatePayload(cwd, options = {}) {
|
|
|
6237
6927
|
};
|
|
6238
6928
|
}
|
|
6239
6929
|
function sendRedirect(response, location) {
|
|
6240
|
-
response.writeHead(302, { Location: location
|
|
6930
|
+
response.writeHead(302, { ...baseSecurityHeaders(), Location: location });
|
|
6241
6931
|
response.end();
|
|
6242
6932
|
}
|
|
6243
6933
|
function findOfficeStation(cwd, stationId) {
|
|
@@ -6256,18 +6946,27 @@ function markOfficeSectionComplete(cwd, stationId, form) {
|
|
|
6256
6946
|
}
|
|
6257
6947
|
markSectionComplete(cwd, section);
|
|
6258
6948
|
}
|
|
6259
|
-
async function handleRequest(cwd, request, response) {
|
|
6949
|
+
async function handleRequest(cwd, request, response, security) {
|
|
6950
|
+
try {
|
|
6951
|
+
assertSecureLocalRequest(request, security);
|
|
6952
|
+
} catch (error) {
|
|
6953
|
+
if (error instanceof LocalHttpRequestError) {
|
|
6954
|
+
sendJson(response, error.statusCode, { error: error.message });
|
|
6955
|
+
return;
|
|
6956
|
+
}
|
|
6957
|
+
throw error;
|
|
6958
|
+
}
|
|
6260
6959
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
6261
6960
|
if (request.method === "GET" && url.pathname === "/setup/wizard") {
|
|
6262
6961
|
sendRedirect(response, "/wizard");
|
|
6263
6962
|
return;
|
|
6264
6963
|
}
|
|
6265
6964
|
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office" || url.pathname === "/setup")) {
|
|
6266
|
-
sendHtml(response, renderSetupOfficeHtmlWithContext(cwd));
|
|
6965
|
+
sendHtml(response, renderSetupOfficeHtmlWithContext(cwd), security);
|
|
6267
6966
|
return;
|
|
6268
6967
|
}
|
|
6269
6968
|
if (request.method === "GET" && url.pathname === "/wizard") {
|
|
6270
|
-
sendHtml(response, renderSetupWizardHtmlWithContext(cwd));
|
|
6969
|
+
sendHtml(response, renderSetupWizardHtmlWithContext(cwd), security);
|
|
6271
6970
|
return;
|
|
6272
6971
|
}
|
|
6273
6972
|
if (request.method === "GET" && url.pathname === "/api/state") {
|
|
@@ -6436,7 +7135,7 @@ async function handleRequest(cwd, request, response) {
|
|
|
6436
7135
|
sendJson(response, 404, { error: "Not found." });
|
|
6437
7136
|
}
|
|
6438
7137
|
function listen(server, host, port) {
|
|
6439
|
-
return new Promise((
|
|
7138
|
+
return new Promise((resolve4, reject) => {
|
|
6440
7139
|
server.once("error", reject);
|
|
6441
7140
|
server.listen(port, host, () => {
|
|
6442
7141
|
const address = server.address();
|
|
@@ -6444,17 +7143,18 @@ function listen(server, host, port) {
|
|
|
6444
7143
|
reject(new Error("Could not determine setup server port."));
|
|
6445
7144
|
return;
|
|
6446
7145
|
}
|
|
6447
|
-
|
|
7146
|
+
resolve4(address.port);
|
|
6448
7147
|
});
|
|
6449
7148
|
});
|
|
6450
7149
|
}
|
|
6451
7150
|
async function startSetupServer(options) {
|
|
6452
7151
|
const host = options.host ?? DEFAULT_HOST;
|
|
7152
|
+
const security = createLocalHttpSecurity(host);
|
|
6453
7153
|
const requestedPort = options.port ?? DEFAULT_PORT;
|
|
6454
7154
|
ensureProjectContextForSetup(options.cwd);
|
|
6455
7155
|
loadOnboardingState(options.cwd);
|
|
6456
7156
|
const server = createServer((request, response) => {
|
|
6457
|
-
handleRequest(options.cwd, request, response).catch((error) => {
|
|
7157
|
+
handleRequest(options.cwd, request, response, security).catch((error) => {
|
|
6458
7158
|
sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
6459
7159
|
});
|
|
6460
7160
|
});
|
|
@@ -6470,16 +7170,18 @@ async function startSetupServer(options) {
|
|
|
6470
7170
|
throw error;
|
|
6471
7171
|
}
|
|
6472
7172
|
}
|
|
7173
|
+
security.port = port;
|
|
6473
7174
|
return {
|
|
6474
|
-
url:
|
|
7175
|
+
url: formatLocalUrl(host, port),
|
|
6475
7176
|
port,
|
|
6476
7177
|
requestedPort,
|
|
6477
7178
|
portFallback,
|
|
6478
7179
|
defaultView: "office",
|
|
6479
|
-
|
|
7180
|
+
csrfToken: security.csrfToken,
|
|
7181
|
+
close: () => new Promise((resolve4, reject) => {
|
|
6480
7182
|
server.close((closeError) => {
|
|
6481
7183
|
if (closeError) reject(closeError);
|
|
6482
|
-
else
|
|
7184
|
+
else resolve4();
|
|
6483
7185
|
});
|
|
6484
7186
|
})
|
|
6485
7187
|
};
|
|
@@ -6488,79 +7190,135 @@ async function startSetupServer(options) {
|
|
|
6488
7190
|
// src/studio/studio-server.ts
|
|
6489
7191
|
import { watch } from "fs";
|
|
6490
7192
|
import { createServer as createServer2 } from "http";
|
|
6491
|
-
import { join as
|
|
7193
|
+
import { join as join28 } from "path";
|
|
6492
7194
|
var DEFAULT_PORT2 = 9331;
|
|
6493
7195
|
var DEFAULT_HOST2 = "127.0.0.1";
|
|
6494
|
-
var
|
|
6495
|
-
|
|
6496
|
-
|
|
7196
|
+
var StudioEventHub = class {
|
|
7197
|
+
constructor(cwd) {
|
|
7198
|
+
this.cwd = cwd;
|
|
7199
|
+
}
|
|
7200
|
+
cwd;
|
|
7201
|
+
streams = /* @__PURE__ */ new Map();
|
|
7202
|
+
subscribe(sessionId, response) {
|
|
7203
|
+
const stream = this.getOrCreate(sessionId);
|
|
7204
|
+
stream.clients.add(response);
|
|
7205
|
+
this.watch(sessionId, stream);
|
|
7206
|
+
return () => {
|
|
7207
|
+
stream.clients.delete(response);
|
|
7208
|
+
if (stream.clients.size === 0) {
|
|
7209
|
+
stream.watcher?.close();
|
|
7210
|
+
this.streams.delete(sessionId);
|
|
7211
|
+
}
|
|
7212
|
+
};
|
|
7213
|
+
}
|
|
7214
|
+
broadcast(sessionId, event, total) {
|
|
7215
|
+
const stream = this.streams.get(sessionId);
|
|
7216
|
+
if (!stream) return;
|
|
7217
|
+
const eventRecord = event && typeof event === "object" ? event : {};
|
|
7218
|
+
const eventKey = eventRecord.eventId ?? eventRecord.sequence;
|
|
7219
|
+
stream.lastEventKey = typeof eventKey === "string" || typeof eventKey === "number" ? String(eventKey) : String(total);
|
|
7220
|
+
const payload = `event: event
|
|
7221
|
+
data: ${JSON.stringify({ sessionId, event, total })}
|
|
7222
|
+
|
|
7223
|
+
`;
|
|
7224
|
+
for (const client of stream.clients) {
|
|
7225
|
+
try {
|
|
7226
|
+
client.write(payload);
|
|
7227
|
+
} catch {
|
|
7228
|
+
stream.clients.delete(client);
|
|
7229
|
+
}
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
close() {
|
|
7233
|
+
for (const stream of this.streams.values()) {
|
|
7234
|
+
stream.watcher?.close();
|
|
7235
|
+
for (const client of stream.clients) {
|
|
7236
|
+
try {
|
|
7237
|
+
client.end();
|
|
7238
|
+
} catch {
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
this.streams.clear();
|
|
7243
|
+
}
|
|
7244
|
+
getOrCreate(sessionId) {
|
|
7245
|
+
const existing = this.streams.get(sessionId);
|
|
7246
|
+
if (existing) return existing;
|
|
7247
|
+
const stream = { clients: /* @__PURE__ */ new Set(), watcher: null, lastEventKey: "" };
|
|
7248
|
+
this.streams.set(sessionId, stream);
|
|
7249
|
+
return stream;
|
|
7250
|
+
}
|
|
7251
|
+
watch(sessionId, stream) {
|
|
7252
|
+
if (stream.watcher) return;
|
|
7253
|
+
const eventsPath2 = join28(this.cwd, COUNCIL_SESSIONS_DIR, sessionId, "events.jsonl");
|
|
7254
|
+
try {
|
|
7255
|
+
stream.watcher = watch(eventsPath2, () => {
|
|
7256
|
+
try {
|
|
7257
|
+
const events = readSessionEvents(this.cwd, sessionId);
|
|
7258
|
+
const latest = events.at(-1);
|
|
7259
|
+
if (!latest) return;
|
|
7260
|
+
const eventKey = String(latest.eventId ?? latest.sequence ?? events.length);
|
|
7261
|
+
if (eventKey === stream.lastEventKey) return;
|
|
7262
|
+
this.broadcast(sessionId, latest, events.length);
|
|
7263
|
+
} catch {
|
|
7264
|
+
}
|
|
7265
|
+
});
|
|
7266
|
+
} catch {
|
|
7267
|
+
stream.watcher = null;
|
|
7268
|
+
}
|
|
7269
|
+
}
|
|
7270
|
+
};
|
|
6497
7271
|
function sendJson2(response, statusCode, payload) {
|
|
6498
7272
|
response.writeHead(statusCode, {
|
|
7273
|
+
...baseSecurityHeaders(),
|
|
6499
7274
|
"Content-Type": "application/json; charset=utf-8",
|
|
6500
|
-
"
|
|
7275
|
+
"Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'"
|
|
6501
7276
|
});
|
|
6502
7277
|
response.end(JSON.stringify(payload));
|
|
6503
7278
|
}
|
|
6504
|
-
function sendHtml2(response, html) {
|
|
7279
|
+
function sendHtml2(response, html, security) {
|
|
7280
|
+
const secured = secureLocalHtml(html, security);
|
|
6505
7281
|
response.writeHead(200, {
|
|
7282
|
+
...baseSecurityHeaders(),
|
|
6506
7283
|
"Content-Type": "text/html; charset=utf-8",
|
|
6507
|
-
"
|
|
6508
|
-
"Content-Security-Policy": "default-src 'none'; connect-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'self'"
|
|
7284
|
+
"Content-Security-Policy": secured.csp
|
|
6509
7285
|
});
|
|
6510
|
-
response.end(
|
|
6511
|
-
}
|
|
6512
|
-
function broadcastSse(event, data) {
|
|
6513
|
-
const payload = `event: ${event}
|
|
6514
|
-
data: ${JSON.stringify(data)}
|
|
6515
|
-
|
|
6516
|
-
`;
|
|
6517
|
-
for (const client of sseClients) {
|
|
6518
|
-
try {
|
|
6519
|
-
client.write(payload);
|
|
6520
|
-
} catch {
|
|
6521
|
-
sseClients.delete(client);
|
|
6522
|
-
}
|
|
6523
|
-
}
|
|
6524
|
-
}
|
|
6525
|
-
function stopWatcher() {
|
|
6526
|
-
if (activeWatcher) {
|
|
6527
|
-
activeWatcher.close();
|
|
6528
|
-
activeWatcher = null;
|
|
6529
|
-
watchedEventsPath = null;
|
|
6530
|
-
}
|
|
6531
|
-
}
|
|
6532
|
-
function watchSessionEvents(cwd, sessionId) {
|
|
6533
|
-
const eventsPath2 = join25(cwd, COUNCIL_SESSIONS_DIR, sessionId, "events.jsonl");
|
|
6534
|
-
if (watchedEventsPath === eventsPath2 && activeWatcher) return;
|
|
6535
|
-
stopWatcher();
|
|
6536
|
-
watchedEventsPath = eventsPath2;
|
|
6537
|
-
try {
|
|
6538
|
-
activeWatcher = watch(eventsPath2, () => {
|
|
6539
|
-
try {
|
|
6540
|
-
const events = readSessionEvents(cwd, sessionId);
|
|
6541
|
-
const latest = events.at(-1);
|
|
6542
|
-
if (latest) broadcastSse("event", { sessionId, event: latest, total: events.length });
|
|
6543
|
-
} catch {
|
|
6544
|
-
}
|
|
6545
|
-
});
|
|
6546
|
-
} catch {
|
|
6547
|
-
watchedEventsPath = null;
|
|
6548
|
-
activeWatcher = null;
|
|
6549
|
-
}
|
|
7286
|
+
response.end(secured.body);
|
|
6550
7287
|
}
|
|
6551
7288
|
function safeSessionId(raw) {
|
|
6552
7289
|
if (!/^[a-z0-9-]+$/i.test(raw)) return null;
|
|
6553
7290
|
return raw;
|
|
6554
7291
|
}
|
|
6555
|
-
function
|
|
6556
|
-
|
|
7292
|
+
async function loadRuntimeService(cwd) {
|
|
7293
|
+
try {
|
|
7294
|
+
const { AgentKitRuntimeService } = await import("@appsforgood/agent-kit-runtime");
|
|
7295
|
+
return new AgentKitRuntimeService(cwd);
|
|
7296
|
+
} catch (error) {
|
|
7297
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7298
|
+
if (message.includes("@appsforgood/agent-kit-runtime") || message.includes("Cannot find package")) {
|
|
7299
|
+
throw new Error("The optional orchestrator runtime is not installed.");
|
|
7300
|
+
}
|
|
7301
|
+
throw error;
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
function handleRequest2(cwd, request, response, security, eventHub) {
|
|
7305
|
+
handleRequestAsync(cwd, request, response, security, eventHub).catch((error) => {
|
|
6557
7306
|
sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
6558
7307
|
});
|
|
6559
7308
|
}
|
|
6560
|
-
async function handleRequestAsync(cwd, request, response) {
|
|
7309
|
+
async function handleRequestAsync(cwd, request, response, security, eventHub) {
|
|
7310
|
+
try {
|
|
7311
|
+
assertSecureLocalRequest(request, security);
|
|
7312
|
+
} catch (error) {
|
|
7313
|
+
if (error instanceof LocalHttpRequestError) {
|
|
7314
|
+
sendJson2(response, error.statusCode, { error: error.message });
|
|
7315
|
+
return;
|
|
7316
|
+
}
|
|
7317
|
+
throw error;
|
|
7318
|
+
}
|
|
6561
7319
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
6562
7320
|
if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/office")) {
|
|
6563
|
-
sendHtml2(response, renderLiveStudioHtmlWithContext(cwd));
|
|
7321
|
+
sendHtml2(response, renderLiveStudioHtmlWithContext(cwd), security);
|
|
6564
7322
|
return;
|
|
6565
7323
|
}
|
|
6566
7324
|
if (request.method === "GET" && url.pathname === "/api/sessions") {
|
|
@@ -6575,6 +7333,121 @@ async function handleRequestAsync(cwd, request, response) {
|
|
|
6575
7333
|
sendJson2(response, 200, { activeSessionId, sessions });
|
|
6576
7334
|
return;
|
|
6577
7335
|
}
|
|
7336
|
+
if (request.method === "GET" && url.pathname === "/api/runtime/runs") {
|
|
7337
|
+
try {
|
|
7338
|
+
const runtime = await loadRuntimeService(cwd);
|
|
7339
|
+
const validation = runtime.validate();
|
|
7340
|
+
sendJson2(response, 200, { runs: runtime.status(), enabled: validation.enabled, warnings: validation.warnings });
|
|
7341
|
+
} catch (error) {
|
|
7342
|
+
sendJson2(response, 503, { error: error instanceof Error ? error.message : String(error) });
|
|
7343
|
+
}
|
|
7344
|
+
return;
|
|
7345
|
+
}
|
|
7346
|
+
if (request.method === "POST" && url.pathname === "/api/runtime/plan") {
|
|
7347
|
+
try {
|
|
7348
|
+
const body = await readJsonBody(request);
|
|
7349
|
+
const goal = typeof body.goal === "string" ? body.goal.trim() : "";
|
|
7350
|
+
const workflowId = typeof body.workflowId === "string" && body.workflowId.trim() ? body.workflowId.trim() : void 0;
|
|
7351
|
+
if (!goal || goal.length > 2e4) {
|
|
7352
|
+
sendJson2(response, 400, { error: "goal must be between 1 and 20000 characters." });
|
|
7353
|
+
return;
|
|
7354
|
+
}
|
|
7355
|
+
const runtime = await loadRuntimeService(cwd);
|
|
7356
|
+
sendJson2(response, 200, { plan: runtime.plan(goal, workflowId) });
|
|
7357
|
+
} catch (error) {
|
|
7358
|
+
sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
7359
|
+
}
|
|
7360
|
+
return;
|
|
7361
|
+
}
|
|
7362
|
+
if (request.method === "POST" && url.pathname === "/api/runtime/runs") {
|
|
7363
|
+
try {
|
|
7364
|
+
const body = await readJsonBody(request);
|
|
7365
|
+
const goal = typeof body.goal === "string" ? body.goal.trim() : "";
|
|
7366
|
+
const workflowId = typeof body.workflowId === "string" && body.workflowId.trim() ? body.workflowId.trim() : void 0;
|
|
7367
|
+
if (!goal || goal.length > 2e4) {
|
|
7368
|
+
sendJson2(response, 400, { error: "goal must be between 1 and 20000 characters." });
|
|
7369
|
+
return;
|
|
7370
|
+
}
|
|
7371
|
+
if (body.acknowledgeDirtyBase !== void 0 && typeof body.acknowledgeDirtyBase !== "boolean") {
|
|
7372
|
+
sendJson2(response, 400, { error: "acknowledgeDirtyBase must be boolean." });
|
|
7373
|
+
return;
|
|
7374
|
+
}
|
|
7375
|
+
const runtime = await loadRuntimeService(cwd);
|
|
7376
|
+
const run = await runtime.run(goal, {
|
|
7377
|
+
...workflowId ? { workflowId } : {},
|
|
7378
|
+
...body.acknowledgeDirtyBase === true ? { acknowledgeDirtyBase: true } : {}
|
|
7379
|
+
});
|
|
7380
|
+
sendJson2(response, 201, { run, events: runtime.events.events(run.runId) });
|
|
7381
|
+
} catch (error) {
|
|
7382
|
+
sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
7383
|
+
}
|
|
7384
|
+
return;
|
|
7385
|
+
}
|
|
7386
|
+
const runtimeRunMatch = url.pathname.match(/^\/api\/runtime\/runs\/([^/]+)$/);
|
|
7387
|
+
if (request.method === "GET" && runtimeRunMatch) {
|
|
7388
|
+
const runId = safeSessionId(runtimeRunMatch[1] ?? "");
|
|
7389
|
+
if (!runId) {
|
|
7390
|
+
sendJson2(response, 400, { error: "Invalid run id." });
|
|
7391
|
+
return;
|
|
7392
|
+
}
|
|
7393
|
+
try {
|
|
7394
|
+
const runtime = await loadRuntimeService(cwd);
|
|
7395
|
+
const run = runtime.status(runId);
|
|
7396
|
+
sendJson2(response, 200, { run, events: runtime.events.events(runId) });
|
|
7397
|
+
} catch (error) {
|
|
7398
|
+
sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
|
|
7399
|
+
}
|
|
7400
|
+
return;
|
|
7401
|
+
}
|
|
7402
|
+
const runtimeDecisionMatch = url.pathname.match(/^\/api\/runtime\/runs\/([^/]+)\/decision$/);
|
|
7403
|
+
if (request.method === "POST" && runtimeDecisionMatch) {
|
|
7404
|
+
const runId = safeSessionId(runtimeDecisionMatch[1] ?? "");
|
|
7405
|
+
if (!runId) {
|
|
7406
|
+
sendJson2(response, 400, { error: "Invalid run id." });
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
try {
|
|
7410
|
+
const body = await readJsonBody(request);
|
|
7411
|
+
if (body.decision !== "approve" && body.decision !== "reject") {
|
|
7412
|
+
sendJson2(response, 400, { error: "decision must be approve or reject." });
|
|
7413
|
+
return;
|
|
7414
|
+
}
|
|
7415
|
+
const actor = typeof body.actor === "string" && body.actor.trim() ? body.actor.trim().slice(0, 200) : "studio-operator";
|
|
7416
|
+
const note = typeof body.note === "string" && body.note.trim() ? body.note.trim().slice(0, 2e3) : void 0;
|
|
7417
|
+
const runtime = await loadRuntimeService(cwd);
|
|
7418
|
+
const current = runtime.status(runId);
|
|
7419
|
+
if (Array.isArray(current) || !current.pendingApproval) {
|
|
7420
|
+
sendJson2(response, 409, { error: `Run ${runId} has no pending approval.` });
|
|
7421
|
+
return;
|
|
7422
|
+
}
|
|
7423
|
+
const run = await runtime.resume(runId, {
|
|
7424
|
+
approvalId: current.pendingApproval.approvalId,
|
|
7425
|
+
decision: body.decision,
|
|
7426
|
+
actor,
|
|
7427
|
+
...note ? { note } : {}
|
|
7428
|
+
});
|
|
7429
|
+
sendJson2(response, 200, { run, events: runtime.events.events(runId) });
|
|
7430
|
+
} catch (error) {
|
|
7431
|
+
sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
7432
|
+
}
|
|
7433
|
+
return;
|
|
7434
|
+
}
|
|
7435
|
+
const runtimeCancelMatch = url.pathname.match(/^\/api\/runtime\/runs\/([^/]+)\/cancel$/);
|
|
7436
|
+
if (request.method === "POST" && runtimeCancelMatch) {
|
|
7437
|
+
const runId = safeSessionId(runtimeCancelMatch[1] ?? "");
|
|
7438
|
+
if (!runId) {
|
|
7439
|
+
sendJson2(response, 400, { error: "Invalid run id." });
|
|
7440
|
+
return;
|
|
7441
|
+
}
|
|
7442
|
+
try {
|
|
7443
|
+
const runtime = await loadRuntimeService(cwd);
|
|
7444
|
+
const run = runtime.cancel(runId);
|
|
7445
|
+
sendJson2(response, 200, { run, events: runtime.events.events(runId) });
|
|
7446
|
+
} catch (error) {
|
|
7447
|
+
sendJson2(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
7448
|
+
}
|
|
7449
|
+
return;
|
|
7450
|
+
}
|
|
6578
7451
|
const eventsMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/events$/);
|
|
6579
7452
|
if (request.method === "GET" && eventsMatch) {
|
|
6580
7453
|
const sessionId = safeSessionId(eventsMatch[1] ?? "");
|
|
@@ -6607,13 +7480,14 @@ async function handleRequestAsync(cwd, request, response) {
|
|
|
6607
7480
|
return;
|
|
6608
7481
|
}
|
|
6609
7482
|
response.writeHead(200, {
|
|
7483
|
+
...baseSecurityHeaders(),
|
|
6610
7484
|
"Content-Type": "text/event-stream; charset=utf-8",
|
|
6611
|
-
"
|
|
6612
|
-
Connection: "keep-alive"
|
|
7485
|
+
"Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'",
|
|
7486
|
+
Connection: "keep-alive",
|
|
7487
|
+
"X-Accel-Buffering": "no"
|
|
6613
7488
|
});
|
|
6614
7489
|
response.write(": connected\n\n");
|
|
6615
|
-
|
|
6616
|
-
watchSessionEvents(cwd, activeId);
|
|
7490
|
+
const unsubscribe = eventHub.subscribe(activeId, response);
|
|
6617
7491
|
try {
|
|
6618
7492
|
const events = readSessionEvents(cwd, activeId);
|
|
6619
7493
|
response.write(`event: snapshot
|
|
@@ -6627,8 +7501,7 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
|
|
|
6627
7501
|
`);
|
|
6628
7502
|
}
|
|
6629
7503
|
request.on("close", () => {
|
|
6630
|
-
|
|
6631
|
-
if (sseClients.size === 0) stopWatcher();
|
|
7504
|
+
unsubscribe();
|
|
6632
7505
|
});
|
|
6633
7506
|
return;
|
|
6634
7507
|
}
|
|
@@ -6656,7 +7529,7 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
|
|
|
6656
7529
|
return;
|
|
6657
7530
|
}
|
|
6658
7531
|
const event = recordSessionNote(cwd, sessionId, agent, text);
|
|
6659
|
-
|
|
7532
|
+
eventHub.broadcast(sessionId, event, readSessionEvents(cwd, sessionId).length);
|
|
6660
7533
|
sendJson2(response, 200, { event });
|
|
6661
7534
|
} catch (error) {
|
|
6662
7535
|
sendJson2(response, 404, { error: error instanceof Error ? error.message : String(error) });
|
|
@@ -6686,7 +7559,7 @@ data: ${JSON.stringify({ sessionId: activeId, events: [] })}
|
|
|
6686
7559
|
sendJson2(response, 404, { error: "Not found." });
|
|
6687
7560
|
}
|
|
6688
7561
|
function listen2(server, host, port) {
|
|
6689
|
-
return new Promise((
|
|
7562
|
+
return new Promise((resolve4, reject) => {
|
|
6690
7563
|
server.once("error", reject);
|
|
6691
7564
|
server.listen(port, host, () => {
|
|
6692
7565
|
const address = server.address();
|
|
@@ -6694,17 +7567,19 @@ function listen2(server, host, port) {
|
|
|
6694
7567
|
reject(new Error("Could not determine studio server port."));
|
|
6695
7568
|
return;
|
|
6696
7569
|
}
|
|
6697
|
-
|
|
7570
|
+
resolve4(address.port);
|
|
6698
7571
|
});
|
|
6699
7572
|
});
|
|
6700
7573
|
}
|
|
6701
7574
|
async function startStudioServer(options) {
|
|
6702
7575
|
const host = options.host ?? DEFAULT_HOST2;
|
|
7576
|
+
const security = createLocalHttpSecurity(host);
|
|
6703
7577
|
const requestedPort = options.port ?? DEFAULT_PORT2;
|
|
6704
7578
|
ensureStudioDirs(options.cwd);
|
|
7579
|
+
const eventHub = new StudioEventHub(options.cwd);
|
|
6705
7580
|
const server = createServer2((request, response) => {
|
|
6706
7581
|
try {
|
|
6707
|
-
handleRequest2(options.cwd, request, response);
|
|
7582
|
+
handleRequest2(options.cwd, request, response, security, eventHub);
|
|
6708
7583
|
} catch (error) {
|
|
6709
7584
|
sendJson2(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
6710
7585
|
}
|
|
@@ -6721,31 +7596,26 @@ async function startStudioServer(options) {
|
|
|
6721
7596
|
throw error;
|
|
6722
7597
|
}
|
|
6723
7598
|
}
|
|
7599
|
+
security.port = port;
|
|
6724
7600
|
return {
|
|
6725
|
-
url:
|
|
7601
|
+
url: formatLocalUrl(host, port),
|
|
6726
7602
|
port,
|
|
6727
7603
|
requestedPort,
|
|
6728
7604
|
portFallback,
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
try {
|
|
6733
|
-
client.end();
|
|
6734
|
-
} catch {
|
|
6735
|
-
}
|
|
6736
|
-
}
|
|
6737
|
-
sseClients.clear();
|
|
7605
|
+
csrfToken: security.csrfToken,
|
|
7606
|
+
close: () => new Promise((resolve4, reject) => {
|
|
7607
|
+
eventHub.close();
|
|
6738
7608
|
server.close((closeError) => {
|
|
6739
7609
|
if (closeError) reject(closeError);
|
|
6740
|
-
else
|
|
7610
|
+
else resolve4();
|
|
6741
7611
|
});
|
|
6742
7612
|
})
|
|
6743
7613
|
};
|
|
6744
7614
|
}
|
|
6745
7615
|
|
|
6746
7616
|
// src/studio/session-checkpoint.ts
|
|
6747
|
-
import { existsSync as
|
|
6748
|
-
import { extname, join as
|
|
7617
|
+
import { existsSync as existsSync25, readFileSync as readFileSync25 } from "fs";
|
|
7618
|
+
import { extname, join as join29 } from "path";
|
|
6749
7619
|
function parseCheckpointMarkdown(content) {
|
|
6750
7620
|
const payload = { notes: [], decisions: [], handoffs: [], outputs: [] };
|
|
6751
7621
|
const sections = content.split(/^## /m).slice(1);
|
|
@@ -6801,7 +7671,7 @@ function parseCheckpointMarkdown(content) {
|
|
|
6801
7671
|
return payload;
|
|
6802
7672
|
}
|
|
6803
7673
|
function parseCheckpointFile(filePath) {
|
|
6804
|
-
const content =
|
|
7674
|
+
const content = readFileSync25(filePath, "utf8");
|
|
6805
7675
|
const ext = extname(filePath).toLowerCase();
|
|
6806
7676
|
if (ext === ".json") {
|
|
6807
7677
|
const parsed = JSON.parse(content);
|
|
@@ -6877,8 +7747,8 @@ function applySessionCheckpoint(cwd, payload) {
|
|
|
6877
7747
|
};
|
|
6878
7748
|
}
|
|
6879
7749
|
function checkpointSessionFromFile(cwd, filePath) {
|
|
6880
|
-
const absolute =
|
|
6881
|
-
if (!
|
|
7750
|
+
const absolute = join29(cwd, filePath);
|
|
7751
|
+
if (!existsSync25(absolute)) throw new Error(`Checkpoint file not found: ${filePath}`);
|
|
6882
7752
|
return applySessionCheckpoint(cwd, parseCheckpointFile(absolute));
|
|
6883
7753
|
}
|
|
6884
7754
|
|
|
@@ -6932,6 +7802,41 @@ var requiredOutputStatuses = ["missing", "partial", "complete", "not-applicable"
|
|
|
6932
7802
|
function isRequiredOutputStatus(value) {
|
|
6933
7803
|
return requiredOutputStatuses.includes(value);
|
|
6934
7804
|
}
|
|
7805
|
+
async function loadRuntimeService2() {
|
|
7806
|
+
try {
|
|
7807
|
+
const { AgentKitRuntimeService } = await import("@appsforgood/agent-kit-runtime");
|
|
7808
|
+
return new AgentKitRuntimeService(process.cwd());
|
|
7809
|
+
} catch (error) {
|
|
7810
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7811
|
+
if (message.includes("@appsforgood/agent-kit-runtime") || message.includes("Cannot find package")) {
|
|
7812
|
+
throw new Error("The optional orchestrator runtime is not installed. Run: npm install --save-dev @appsforgood/agent-kit-runtime");
|
|
7813
|
+
}
|
|
7814
|
+
throw error;
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7817
|
+
function printRuntimeRecord(record) {
|
|
7818
|
+
line(`${style.bold(record.runId)} ${record.status}`);
|
|
7819
|
+
line(`workflow: ${record.workflowId}`);
|
|
7820
|
+
if (record.branchName) line(`branch: ${record.branchName}`);
|
|
7821
|
+
if (record.commit) line(`commit: ${record.commit}`);
|
|
7822
|
+
if (record.pendingApproval) {
|
|
7823
|
+
line(`approval: ${record.pendingApproval.approvalId} (${record.pendingApproval.risk})`);
|
|
7824
|
+
detail(record.pendingApproval.title);
|
|
7825
|
+
}
|
|
7826
|
+
if (record.error) line(style.fail(`failure: ${record.error}`));
|
|
7827
|
+
}
|
|
7828
|
+
async function readCredentialValue() {
|
|
7829
|
+
if (!process.stdin.isTTY) {
|
|
7830
|
+
const value2 = readFileSync26(0, "utf8").replace(/[\r\n]+$/, "");
|
|
7831
|
+
if (!value2) throw new Error("Credential value was empty.");
|
|
7832
|
+
return value2;
|
|
7833
|
+
}
|
|
7834
|
+
const clack = await import("@clack/prompts");
|
|
7835
|
+
const value = await clack.password({ message: "Credential value" });
|
|
7836
|
+
if (clack.isCancel(value)) throw new Error("Credential entry cancelled.");
|
|
7837
|
+
if (typeof value !== "string" || !value) throw new Error("Credential value was empty.");
|
|
7838
|
+
return value;
|
|
7839
|
+
}
|
|
6935
7840
|
program.name("agent-kit").description("Next.js + Supabase agent, skill, docs, design, and research kit.").version(PACKAGE_VERSION);
|
|
6936
7841
|
async function runGuidedContextPrompts(cwd) {
|
|
6937
7842
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return;
|
|
@@ -6953,11 +7858,11 @@ async function runGuidedContextPrompts(cwd) {
|
|
|
6953
7858
|
if (typeof answer === "string" && answer.trim()) answers[question.key] = answer.trim();
|
|
6954
7859
|
}
|
|
6955
7860
|
if (Object.keys(answers).length > 0) {
|
|
6956
|
-
const contextPath =
|
|
6957
|
-
if (
|
|
6958
|
-
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"));
|
|
6959
7864
|
Object.assign(context2, answers);
|
|
6960
|
-
|
|
7865
|
+
writeFileSync3(contextPath, `${JSON.stringify(context2, null, 2)}
|
|
6961
7866
|
`);
|
|
6962
7867
|
renderProjectContext(cwd);
|
|
6963
7868
|
}
|
|
@@ -6978,9 +7883,9 @@ async function runSetupServer(options) {
|
|
|
6978
7883
|
console.log(`Pixel office (default): ${handle.url}/ | Form fallback: ${handle.url}/wizard`);
|
|
6979
7884
|
console.log("Pick Quick, Standard, or Complete on first visit. Press Ctrl+C to stop.");
|
|
6980
7885
|
if (options.open) void openBrowser(`${handle.url}/`);
|
|
6981
|
-
await new Promise((
|
|
7886
|
+
await new Promise((resolve4) => {
|
|
6982
7887
|
const shutdown = () => {
|
|
6983
|
-
void handle.close().finally(
|
|
7888
|
+
void handle.close().finally(resolve4);
|
|
6984
7889
|
};
|
|
6985
7890
|
process.once("SIGINT", shutdown);
|
|
6986
7891
|
process.once("SIGTERM", shutdown);
|
|
@@ -7045,8 +7950,20 @@ program.command("init").description("Install agent-kit docs and library files in
|
|
|
7045
7950
|
await runSetupServer({ port: 9321, host: "127.0.0.1", open: Boolean(options.open) });
|
|
7046
7951
|
}
|
|
7047
7952
|
});
|
|
7048
|
-
program.command("audit").description("Audit an existing project for agent-kit coverage gaps.").option("--json", "Print machine-readable JSON output.").option("--min-readiness <level>", `Exit non-zero unless readiness is at least this level: ${READINESS_ORDER.join(", ")}.`).action((options) => {
|
|
7049
|
-
|
|
7953
|
+
program.command("audit").description("Audit an existing project for agent-kit coverage gaps.").option("--json", "Print machine-readable JSON output.").option("--schema-version <version>", "JSON audit schema version: 1 or 2.", "1").option("--format <format>", "Output format: human, json, or sarif.").option("--min-readiness <level>", `Exit non-zero unless readiness is at least this level: ${READINESS_ORDER.join(", ")}.`).action((options) => {
|
|
7954
|
+
if (options.schemaVersion !== "1" && options.schemaVersion !== "2") {
|
|
7955
|
+
fail(`Invalid --schema-version value "${options.schemaVersion}". Expected 1 or 2.`);
|
|
7956
|
+
process.exitCode = 1;
|
|
7957
|
+
return;
|
|
7958
|
+
}
|
|
7959
|
+
const format = options.format ?? (options.json ? "json" : "human");
|
|
7960
|
+
if (!(/* @__PURE__ */ new Set(["human", "json", "sarif"])).has(format)) {
|
|
7961
|
+
fail(`Invalid --format value "${format}". Expected human, json, or sarif.`);
|
|
7962
|
+
process.exitCode = 1;
|
|
7963
|
+
return;
|
|
7964
|
+
}
|
|
7965
|
+
const reportV2 = options.schemaVersion === "2" || format === "sarif" ? createAuditReportV2(process.cwd()) : void 0;
|
|
7966
|
+
const report2 = reportV2 ?? createAuditReport(process.cwd());
|
|
7050
7967
|
let minimumReadiness;
|
|
7051
7968
|
if (options.minReadiness) {
|
|
7052
7969
|
if (!isAuditReadinessLevel(options.minReadiness)) {
|
|
@@ -7056,7 +7973,9 @@ program.command("audit").description("Audit an existing project for agent-kit co
|
|
|
7056
7973
|
}
|
|
7057
7974
|
minimumReadiness = options.minReadiness;
|
|
7058
7975
|
}
|
|
7059
|
-
if (
|
|
7976
|
+
if (format === "sarif") {
|
|
7977
|
+
printJson(auditReportToSarif(reportV2));
|
|
7978
|
+
} else if (format === "json") {
|
|
7060
7979
|
printJson(report2);
|
|
7061
7980
|
} else {
|
|
7062
7981
|
const readinessStyle = report2.summary.fail > 0 ? style.fail : report2.summary.warn > 0 ? style.warn : style.pass;
|
|
@@ -7408,6 +8327,122 @@ correction.command("propose-upstream <id>").description("Create an upstream prop
|
|
|
7408
8327
|
if (options.json) printJson(result);
|
|
7409
8328
|
else line(`Created upstream proposal from correction ${id}.`);
|
|
7410
8329
|
});
|
|
8330
|
+
var orchestrate = program.command("orchestrate").description("Run checkpointed council workflows with the optional local runtime.");
|
|
8331
|
+
orchestrate.command("validate").description("Validate orchestrator config, roster references, bounds, and credential references without calling providers.").option("--json", "Print machine-readable JSON output.").action(async (options) => {
|
|
8332
|
+
const result = (await loadRuntimeService2()).validate();
|
|
8333
|
+
if (options.json) printJson(result);
|
|
8334
|
+
else {
|
|
8335
|
+
heading("agent-kit orchestrator");
|
|
8336
|
+
line(`status: ${result.valid ? "valid" : "invalid"}`);
|
|
8337
|
+
line(`enabled: ${result.enabled}`);
|
|
8338
|
+
line(`roster: ${result.rosterId}`);
|
|
8339
|
+
line(`workflows: ${result.workflows.join(", ")}`);
|
|
8340
|
+
line(`providers: ${result.providers.join(", ") || "none"}`);
|
|
8341
|
+
for (const warning of result.warnings) detail(`warning: ${warning}`);
|
|
8342
|
+
}
|
|
8343
|
+
});
|
|
8344
|
+
orchestrate.command("plan <goal...>").description("Compile a deterministic offline workflow plan from the installed roster.").option("--workflow <workflow>", "Use an explicit roster workflow id.").option("--json", "Print machine-readable JSON output.").action(async (goalParts, options) => {
|
|
8345
|
+
const result = (await loadRuntimeService2()).plan(goalParts.join(" "), options.workflow);
|
|
8346
|
+
if (options.json) printJson(result);
|
|
8347
|
+
else {
|
|
8348
|
+
heading(`Workflow: ${result.workflowId}`);
|
|
8349
|
+
line(`sequence: ${result.sequence.join(" -> ")}`);
|
|
8350
|
+
line(`council: ${result.council.join(", ")}`);
|
|
8351
|
+
line(`approvals: ${result.approvals.join(", ") || "none"}`);
|
|
8352
|
+
line(`model aliases: ${result.modelAliases.join(", ") || "none"}`);
|
|
8353
|
+
line(`MCP servers: ${result.mcpServers.join(", ") || "none"}`);
|
|
8354
|
+
}
|
|
8355
|
+
});
|
|
8356
|
+
orchestrate.command("run <goal...>").description("Start a foreground checkpointed workflow in an isolated Git worktree.").option("--workflow <workflow>", "Use an explicit roster workflow id.").option("--acknowledge-dirty-base", "Acknowledge that current uncommitted changes will be excluded from the worktree.").option("--json", "Print machine-readable JSON output.").action(async (goalParts, options) => {
|
|
8357
|
+
const result = await (await loadRuntimeService2()).run(goalParts.join(" "), {
|
|
8358
|
+
...options.workflow ? { workflowId: options.workflow } : {},
|
|
8359
|
+
...options.acknowledgeDirtyBase ? { acknowledgeDirtyBase: true } : {}
|
|
8360
|
+
});
|
|
8361
|
+
if (options.json) printJson(result);
|
|
8362
|
+
else printRuntimeRecord(result);
|
|
8363
|
+
});
|
|
8364
|
+
orchestrate.command("status [run-id]").description("Show one runtime run or list recent runs.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
|
|
8365
|
+
const result = (await loadRuntimeService2()).status(runId);
|
|
8366
|
+
if (options.json) printJson(result);
|
|
8367
|
+
else if (Array.isArray(result)) {
|
|
8368
|
+
if (result.length === 0) line("No runtime runs found.");
|
|
8369
|
+
for (const record of result) printRuntimeRecord(record);
|
|
8370
|
+
} else {
|
|
8371
|
+
printRuntimeRecord(result);
|
|
8372
|
+
}
|
|
8373
|
+
});
|
|
8374
|
+
orchestrate.command("approve <run-id>").description("Approve the pending gate and resume the checkpointed workflow.").option("--actor <actor>", "Decision actor.", process.env.USER ?? "operator").option("--note <note>", "Decision note.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
|
|
8375
|
+
const runtime = await loadRuntimeService2();
|
|
8376
|
+
const record = runtime.status(runId);
|
|
8377
|
+
if (Array.isArray(record) || !record.pendingApproval) throw new Error(`Run ${runId} has no pending approval.`);
|
|
8378
|
+
const result = await runtime.resume(runId, {
|
|
8379
|
+
approvalId: record.pendingApproval.approvalId,
|
|
8380
|
+
decision: "approve",
|
|
8381
|
+
actor: options.actor,
|
|
8382
|
+
...options.note ? { note: options.note } : {}
|
|
8383
|
+
});
|
|
8384
|
+
if (options.json) printJson(result);
|
|
8385
|
+
else printRuntimeRecord(result);
|
|
8386
|
+
});
|
|
8387
|
+
orchestrate.command("resume <run-id>").description("Resume a pending gate with an explicit approve or reject decision.").requiredOption("--decision <decision>", "approve or reject.").option("--actor <actor>", "Decision actor.", process.env.USER ?? "operator").option("--note <note>", "Decision note.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
|
|
8388
|
+
if (options.decision !== "approve" && options.decision !== "reject") throw new Error("--decision must be approve or reject.");
|
|
8389
|
+
const runtime = await loadRuntimeService2();
|
|
8390
|
+
const record = runtime.status(runId);
|
|
8391
|
+
if (Array.isArray(record) || !record.pendingApproval) throw new Error(`Run ${runId} has no pending approval.`);
|
|
8392
|
+
const result = await runtime.resume(runId, {
|
|
8393
|
+
approvalId: record.pendingApproval.approvalId,
|
|
8394
|
+
decision: options.decision,
|
|
8395
|
+
actor: options.actor,
|
|
8396
|
+
...options.note ? { note: options.note } : {}
|
|
8397
|
+
});
|
|
8398
|
+
if (options.json) printJson(result);
|
|
8399
|
+
else printRuntimeRecord(result);
|
|
8400
|
+
});
|
|
8401
|
+
orchestrate.command("cancel <run-id>").description("Cancel a planned or approval-paused run.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
|
|
8402
|
+
const result = (await loadRuntimeService2()).cancel(runId);
|
|
8403
|
+
if (options.json) printJson(result);
|
|
8404
|
+
else printRuntimeRecord(result);
|
|
8405
|
+
});
|
|
8406
|
+
orchestrate.command("export <run-id>").description("Export redacted JSONL-backed runtime evidence as Markdown.").option("--output <path>", "Project-relative output path.").option("--json", "Print machine-readable JSON output.").action(async (runId, options) => {
|
|
8407
|
+
const evidence = (await loadRuntimeService2()).exportEvidence(runId);
|
|
8408
|
+
if (!options.output) {
|
|
8409
|
+
line(evidence.trimEnd());
|
|
8410
|
+
return;
|
|
8411
|
+
}
|
|
8412
|
+
if (isAbsolute2(options.output)) throw new Error("--output must be project-relative.");
|
|
8413
|
+
const root = resolve3(process.cwd());
|
|
8414
|
+
const output = resolve3(root, options.output);
|
|
8415
|
+
const relationship = relative2(root, output);
|
|
8416
|
+
if (relationship.startsWith("..") || isAbsolute2(relationship)) throw new Error("--output must remain inside the project.");
|
|
8417
|
+
mkdirSync3(dirname4(output), { recursive: true });
|
|
8418
|
+
writeFileSync3(output, evidence, { mode: 384 });
|
|
8419
|
+
if (options.json) printJson({ runId, output: relationship.replace(/\\/g, "/") });
|
|
8420
|
+
else line(`Exported ${runId} evidence to ${relationship}.`);
|
|
8421
|
+
});
|
|
8422
|
+
var provider = program.command("provider").description("Probe configured orchestrator model providers.");
|
|
8423
|
+
provider.command("probe [provider-id]").description("Probe one provider or all configured providers.").option("--json", "Print machine-readable JSON output.").action(async (providerId, options) => {
|
|
8424
|
+
const result = await (await loadRuntimeService2()).probeProvider(providerId);
|
|
8425
|
+
if (options.json) printJson(result);
|
|
8426
|
+
else
|
|
8427
|
+
for (const probe of result) line(`${probe.available ? "PASS" : "FAIL"} ${probe.providerId} ${probe.latencyMs}ms${probe.error ? `: ${probe.error}` : ""}`);
|
|
8428
|
+
});
|
|
8429
|
+
var credential = program.command("credential").description("Manage orchestrator credentials in the OS keychain.");
|
|
8430
|
+
credential.command("set <reference>").description("Read a credential from a masked prompt or stdin and store a keychain: reference.").action(async (reference) => {
|
|
8431
|
+
const value = await readCredentialValue();
|
|
8432
|
+
await (await loadRuntimeService2()).setCredential(reference, value);
|
|
8433
|
+
line(`Stored ${reference} in the OS keychain.`);
|
|
8434
|
+
});
|
|
8435
|
+
credential.command("delete <reference>").description("Delete a keychain credential reference.").option("--json", "Print machine-readable JSON output.").action(async (reference, options) => {
|
|
8436
|
+
const deleted = await (await loadRuntimeService2()).deleteCredential(reference);
|
|
8437
|
+
if (options.json) printJson({ reference, deleted });
|
|
8438
|
+
else line(deleted ? `Deleted ${reference}.` : `No credential existed for ${reference}.`);
|
|
8439
|
+
});
|
|
8440
|
+
var mcp = program.command("mcp").description("Probe explicitly configured MCP servers.");
|
|
8441
|
+
mcp.command("probe <server>").description("Connect, ping, and list allowlisted tools for one MCP server.").option("--json", "Print machine-readable JSON output.").action(async (server, options) => {
|
|
8442
|
+
const result = await (await loadRuntimeService2()).probeMcp(server);
|
|
8443
|
+
if (options.json) printJson(result);
|
|
8444
|
+
else printJson(result);
|
|
8445
|
+
});
|
|
7411
8446
|
var studio = program.command("studio").description("Export and serve local Agent Studio views.");
|
|
7412
8447
|
async function runStudioServer(options) {
|
|
7413
8448
|
const handle = await startStudioServer({
|
|
@@ -7421,9 +8456,9 @@ async function runStudioServer(options) {
|
|
|
7421
8456
|
console.log(`Agent Kit v${PACKAGE_VERSION} \u2014 live studio at ${handle.url}/`);
|
|
7422
8457
|
console.log("SSE: GET /api/events/stream | Press Ctrl+C to stop.");
|
|
7423
8458
|
if (options.open) void openBrowser(`${handle.url}/`);
|
|
7424
|
-
await new Promise((
|
|
8459
|
+
await new Promise((resolve4) => {
|
|
7425
8460
|
const shutdown = () => {
|
|
7426
|
-
void handle.close().finally(
|
|
8461
|
+
void handle.close().finally(resolve4);
|
|
7427
8462
|
};
|
|
7428
8463
|
process.once("SIGINT", shutdown);
|
|
7429
8464
|
process.once("SIGTERM", shutdown);
|