@raishin/vanguard-frontier-agentic 2.5.0 → 2.7.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/.agents/tasks/task-dynamic-kiro-powers/2025-01-24-120000-review.md +92 -0
- package/.agents/tasks/task-dynamic-kiro-powers/context.json +22 -0
- package/.agents/tasks/task-dynamic-kiro-powers/features/FEAT-001.json +34 -0
- package/.agents/tasks/task-dynamic-kiro-powers/task.json +14 -0
- package/.agents/tasks/task-jekyll-docs-site/2025-07-17-review.md +118 -0
- package/.agents/tasks/task-jekyll-docs-site/context.json +30 -0
- package/.agents/tasks/task-jekyll-docs-site/features/FEAT-001.json +28 -0
- package/.agents/tasks/task-jekyll-docs-site/features/FEAT-002.json +44 -0
- package/.agents/tasks/task-jekyll-docs-site/task.json +14 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +1 -1
- package/README.md +2 -0
- package/catalog/asset-integrity.json +129 -29
- package/package.json +3 -1
- package/plugins/vanguard-frontier-agentic/.codex-plugin/plugin.json +3 -2
- package/plugins/vanguard-frontier-agentic/skills/vanguard-frontier-agentic-install/SKILL.md +37 -0
- package/powers/README.md +28 -10
- package/powers/vanguard-argocd/POWER.md +40 -0
- package/powers/vanguard-backstage/POWER.md +40 -0
- package/powers/vanguard-cert-manager/POWER.md +40 -0
- package/powers/vanguard-cilium/POWER.md +40 -0
- package/powers/vanguard-dotnet/POWER.md +41 -0
- package/powers/vanguard-falco/POWER.md +40 -0
- package/powers/vanguard-fluxcd/POWER.md +40 -0
- package/powers/vanguard-generic/POWER.md +40 -0
- package/powers/vanguard-hr/POWER.md +41 -0
- package/powers/vanguard-istio/POWER.md +40 -0
- package/powers/vanguard-kyverno/POWER.md +40 -0
- package/powers/vanguard-legal/POWER.md +41 -0
- package/powers/vanguard-marketing/POWER.md +41 -0
- package/powers/vanguard-multi-cloud/POWER.md +41 -0
- package/powers/vanguard-opentelemetry/POWER.md +40 -0
- package/powers/vanguard-prometheus/POWER.md +40 -0
- package/powers/vanguard-sigstore/POWER.md +40 -0
- package/scripts/export-marketplace-agents.mjs +26 -0
- package/scripts/generate-kiro-powers.mjs +360 -5
- package/scripts/install-codex-home.mjs +95 -0
- package/tests/test-codex-plugin-marketplace-install.test.mjs +132 -0
- package/tests/test-vfa-export-coverage.test.mjs +108 -0
- package/tests/validate-codex-marketplace.py +23 -1
|
@@ -212,6 +212,239 @@ const PROVIDERS = {
|
|
|
212
212
|
|
|
213
213
|
const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
|
|
214
214
|
|
|
215
|
+
// --- Dynamic provider discovery and derivation ---
|
|
216
|
+
|
|
217
|
+
/** Special-case display name mappings for providers not in PROVIDERS. */
|
|
218
|
+
const DISPLAY_NAME_OVERRIDES = {
|
|
219
|
+
dotnet: ".NET",
|
|
220
|
+
hr: "HR",
|
|
221
|
+
fluxcd: "FluxCD",
|
|
222
|
+
argocd: "ArgoCD",
|
|
223
|
+
opentelemetry: "OpenTelemetry",
|
|
224
|
+
"cert-manager": "Cert-Manager",
|
|
225
|
+
"multi-cloud": "Multi-Cloud",
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/** Pre-authored keyword sets for derived providers. */
|
|
229
|
+
const DERIVED_KEYWORDS = {
|
|
230
|
+
argocd: ["argocd", "gitops", "progressive-delivery", "application-sync"],
|
|
231
|
+
dotnet: ["dotnet", "csharp", "aspnet-core", "ef-core", "nuget"],
|
|
232
|
+
marketing: ["marketing-governance", "consent-compliance", "advertising-fairness", "email-authentication"],
|
|
233
|
+
hr: ["hr-governance", "employment-risk", "compensation-equity", "recruiting"],
|
|
234
|
+
legal: ["legal-risk", "contract-review", "privacy-compliance", "regulatory"],
|
|
235
|
+
generic: ["test-quality", "ci-pipeline", "helm-chart", "manifest-review"],
|
|
236
|
+
"multi-cloud": ["finops", "cloud-pricing", "cost-optimization", "reserved-instances"],
|
|
237
|
+
backstage: ["backstage", "scaffolder", "software-templates", "developer-portal"],
|
|
238
|
+
"cert-manager": ["cert-manager", "x509", "certificate-lifecycle", "pki"],
|
|
239
|
+
cilium: ["cilium", "network-policy", "ebpf", "cluster-mesh"],
|
|
240
|
+
falco: ["falco", "runtime-threat", "syscall-rules", "container-security"],
|
|
241
|
+
fluxcd: ["fluxcd", "gitops", "kustomization", "helm-release"],
|
|
242
|
+
istio: ["istio", "service-mesh", "ambient-mesh", "mtls"],
|
|
243
|
+
kyverno: ["kyverno", "admission-policy", "cluster-policy", "policy-enforcement"],
|
|
244
|
+
opentelemetry: ["opentelemetry", "otel-collector", "tracing", "observability-pipeline"],
|
|
245
|
+
prometheus: ["prometheus", "alertmanager", "metrics-cardinality", "scrape-config"],
|
|
246
|
+
sigstore: ["sigstore", "cosign", "supply-chain-integrity", "image-signing"],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Discover all unique providers from the catalog where at least one agent
|
|
251
|
+
* has 'kiro' in its harnesses array.
|
|
252
|
+
*/
|
|
253
|
+
function discoverKiroProviders() {
|
|
254
|
+
const providers = new Set();
|
|
255
|
+
for (const entry of catalog) {
|
|
256
|
+
if (
|
|
257
|
+
entry.type === "agent" &&
|
|
258
|
+
Array.isArray(entry.harnesses) &&
|
|
259
|
+
entry.harnesses.includes("kiro")
|
|
260
|
+
) {
|
|
261
|
+
providers.add(entry.provider);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return [...providers].sort();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Title-case a provider name, handling special cases.
|
|
269
|
+
*/
|
|
270
|
+
function titleCaseProvider(provider) {
|
|
271
|
+
if (DISPLAY_NAME_OVERRIDES[provider]) return DISPLAY_NAME_OVERRIDES[provider];
|
|
272
|
+
return provider
|
|
273
|
+
.split("-")
|
|
274
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
275
|
+
.join("-");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Derive a topic summary from agent IDs for description generation.
|
|
280
|
+
*/
|
|
281
|
+
function deriveTopics(entries) {
|
|
282
|
+
const topics = entries
|
|
283
|
+
.map((e) => e.id)
|
|
284
|
+
.filter((id) => !id.endsWith("-maestro-agent"))
|
|
285
|
+
.map((id) => {
|
|
286
|
+
// Strip provider prefix and -agent / -review-agent suffix
|
|
287
|
+
let topic = id
|
|
288
|
+
.replace(/-review-agent$/, "")
|
|
289
|
+
.replace(/-agent$/, "")
|
|
290
|
+
.replace(/-run-agent$/, "");
|
|
291
|
+
// Remove known provider prefixes
|
|
292
|
+
const prefixes = [
|
|
293
|
+
"dotnet-", "hr-", "legal-", "marketing-", "finops-",
|
|
294
|
+
"argocd-", "backstage-", "cert-manager-", "cilium-",
|
|
295
|
+
"falco-", "fluxcd-", "istio-", "kyverno-",
|
|
296
|
+
"opentelemetry-", "prometheus-", "sigstore-",
|
|
297
|
+
];
|
|
298
|
+
for (const pfx of prefixes) {
|
|
299
|
+
if (topic.startsWith(pfx)) {
|
|
300
|
+
topic = topic.slice(pfx.length);
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return topic.replace(/-/g, " ");
|
|
305
|
+
})
|
|
306
|
+
.slice(0, 4);
|
|
307
|
+
return topics.join(", ");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Auto-generate steering content for a provider NOT in the hardcoded
|
|
312
|
+
* PROVIDERS object.
|
|
313
|
+
*/
|
|
314
|
+
function deriveProviderConfig(provider, catalogEntries) {
|
|
315
|
+
const displayLabel = titleCaseProvider(provider);
|
|
316
|
+
const displayName = `Vanguard Frontier \u2014 ${displayLabel}`;
|
|
317
|
+
|
|
318
|
+
const entries = catalogEntries.filter(
|
|
319
|
+
(e) => e.type === "agent" && e.provider === provider,
|
|
320
|
+
);
|
|
321
|
+
const maestro = entries.find((e) => e.id.endsWith("-maestro-agent"));
|
|
322
|
+
const liveGuards = entries.filter((e) => /-live-/.test(e.id));
|
|
323
|
+
|
|
324
|
+
// Build description (max 3 sentences)
|
|
325
|
+
let description;
|
|
326
|
+
if (maestro && entries.length > 2) {
|
|
327
|
+
const topics = deriveTopics(entries);
|
|
328
|
+
description = `Curated ${displayLabel} agents for ${topics}. Routes via ${maestro.id} to specialist agents based on task scope. Static review only; no live mutations.`;
|
|
329
|
+
} else if (entries.length === 1) {
|
|
330
|
+
// Single agent, no maestro
|
|
331
|
+
const summary = entries[0].summary || "";
|
|
332
|
+
// Split into sentences - skip "Agent for <id>." prefix if present
|
|
333
|
+
const sentences = summary.split(/(?<=[.!?])\s/);
|
|
334
|
+
let useSentence = sentences[0] || "";
|
|
335
|
+
if (/^Agent for\s/i.test(useSentence) && sentences.length > 1) {
|
|
336
|
+
useSentence = sentences[1];
|
|
337
|
+
}
|
|
338
|
+
// Remove trailing period for reassembly
|
|
339
|
+
useSentence = useSentence.replace(/\.$/, "");
|
|
340
|
+
// Strip leading "Static review of" / "Review" prefix to avoid doubling
|
|
341
|
+
let core = useSentence
|
|
342
|
+
.replace(/^Static,?\s+evidence-gated\s+review\s+of\s+/i, "")
|
|
343
|
+
.replace(/^Static\s+review\s+of\s+/i, "")
|
|
344
|
+
.replace(/^Review\s+(a\s+)?/i, "");
|
|
345
|
+
// Truncate if too long, respecting word boundaries
|
|
346
|
+
if (core.length > 120) {
|
|
347
|
+
const truncated = core.substring(0, 117);
|
|
348
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
349
|
+
core = (lastSpace > 0 ? truncated.substring(0, lastSpace) : truncated) + "...";
|
|
350
|
+
}
|
|
351
|
+
const sep = core.endsWith("...") ? " " : ". ";
|
|
352
|
+
description = `Reviews ${core.charAt(0).toLowerCase() + core.slice(1)}${sep}Static review only; no live mutations.`;
|
|
353
|
+
} else {
|
|
354
|
+
// Multiple agents, no maestro
|
|
355
|
+
const topics = deriveTopics(entries);
|
|
356
|
+
description = `Curated ${displayLabel} review agents covering ${topics}. Reference agents directly under agents/${provider}/. Static review only; no live mutations.`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Keywords
|
|
360
|
+
const keywords = DERIVED_KEYWORDS[provider] || [
|
|
361
|
+
provider,
|
|
362
|
+
"static-review",
|
|
363
|
+
"configuration-audit",
|
|
364
|
+
"best-practices",
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
// Invariants
|
|
368
|
+
const invariants = [];
|
|
369
|
+
if (liveGuards.length > 0) {
|
|
370
|
+
invariants.push(
|
|
371
|
+
`Live-guard agents (${provider}-live-*) must never be auto-dispatched; require explicit approval and rollback plan.`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (maestro) {
|
|
375
|
+
invariants.push(
|
|
376
|
+
`Route all tasks through ${maestro.id} for proper classification and dispatch.`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
invariants.push(
|
|
380
|
+
"Static review only -- agents analyze configuration and provide findings without mutating live systems.",
|
|
381
|
+
);
|
|
382
|
+
// Add domain-specific invariants
|
|
383
|
+
if (provider === "dotnet") {
|
|
384
|
+
invariants.push("Review covers language runtime, frameworks, data access, testing, and supply-chain integrity.");
|
|
385
|
+
} else if (provider === "hr") {
|
|
386
|
+
invariants.push("All findings must respect employee privacy and data-minimization principles.");
|
|
387
|
+
} else if (provider === "legal") {
|
|
388
|
+
invariants.push("Agents provide risk-flagging only; output is not legal advice and does not create attorney-client privilege.");
|
|
389
|
+
} else if (provider === "marketing") {
|
|
390
|
+
invariants.push("Review covers consent, privacy, fairness, and regulatory compliance for marketing systems.");
|
|
391
|
+
} else if (provider === "multi-cloud") {
|
|
392
|
+
invariants.push("Cost recommendations are estimates based on public pricing; verify against actual billing before acting.");
|
|
393
|
+
} else if (provider === "generic") {
|
|
394
|
+
invariants.push("Agents are provider-agnostic and focus on CI, Helm, manifest, and test-quality patterns.");
|
|
395
|
+
} else if (provider === "argocd") {
|
|
396
|
+
invariants.push("Sync and rollout strategies must be validated against the target cluster GitOps workflow.");
|
|
397
|
+
} else if (provider === "backstage") {
|
|
398
|
+
invariants.push("Template parameters and scaffolder actions must be reviewed for injection and secret-exposure risks.");
|
|
399
|
+
} else if (provider === "cert-manager") {
|
|
400
|
+
invariants.push("Certificate renewal windows and issuer trust chains must be validated before any policy change.");
|
|
401
|
+
} else if (provider === "cilium") {
|
|
402
|
+
invariants.push("Network policies must be reviewed for unintended traffic blocking across namespaces and cluster-mesh endpoints.");
|
|
403
|
+
} else if (provider === "falco") {
|
|
404
|
+
invariants.push("Rule changes must be evaluated for false-positive rate impact on production alerting.");
|
|
405
|
+
} else if (provider === "fluxcd") {
|
|
406
|
+
invariants.push("Kustomization and HelmRelease reconciliation intervals must align with the GitOps change cadence.");
|
|
407
|
+
} else if (provider === "istio") {
|
|
408
|
+
invariants.push("Service mesh policies affect traffic routing cluster-wide; review blast radius before changes.");
|
|
409
|
+
} else if (provider === "kyverno") {
|
|
410
|
+
invariants.push("Cluster-scoped policies can reject legitimate workloads; validate against existing deployments before applying.");
|
|
411
|
+
} else if (provider === "opentelemetry") {
|
|
412
|
+
invariants.push("Collector pipeline changes affect observability for all instrumented services; review cardinality impact.");
|
|
413
|
+
} else if (provider === "prometheus") {
|
|
414
|
+
invariants.push("Alerting rule and scrape config changes affect monitoring coverage; review for metric-name collisions.");
|
|
415
|
+
} else if (provider === "sigstore") {
|
|
416
|
+
invariants.push("Supply-chain policy changes can block valid deployments; verify cosign keyless trust roots before enforcement.");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return { displayName, description, keywords, invariants };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Build a merged map combining hand-authored PROVIDERS with auto-derived
|
|
424
|
+
* entries for all kiro-enabled providers in the catalog.
|
|
425
|
+
*/
|
|
426
|
+
function buildMergedProviders() {
|
|
427
|
+
const kiroProviders = discoverKiroProviders();
|
|
428
|
+
const merged = {};
|
|
429
|
+
|
|
430
|
+
for (const provider of kiroProviders) {
|
|
431
|
+
if (PROVIDERS[provider]) {
|
|
432
|
+
merged[provider] = PROVIDERS[provider];
|
|
433
|
+
} else {
|
|
434
|
+
merged[provider] = deriveProviderConfig(provider, catalog);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Sort alphabetically for deterministic output
|
|
439
|
+
const sorted = {};
|
|
440
|
+
for (const key of Object.keys(merged).sort()) {
|
|
441
|
+
sorted[key] = merged[key];
|
|
442
|
+
}
|
|
443
|
+
return sorted;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const allProviders = buildMergedProviders();
|
|
447
|
+
|
|
215
448
|
function summarize(provider) {
|
|
216
449
|
const entries = catalog.filter(
|
|
217
450
|
(e) => e.type === "agent" && e.provider === provider,
|
|
@@ -254,7 +487,9 @@ function renderPower(provider, cfg) {
|
|
|
254
487
|
|
|
255
488
|
const adapterNote =
|
|
256
489
|
kiroAvailable === total
|
|
257
|
-
?
|
|
490
|
+
? total === 1
|
|
491
|
+
? `The single agent in this provider ships a Kiro adapter (\`harnesses/kiro-ide.agent.md\`, \`kiro-cli.agent.json\`).`
|
|
492
|
+
: `All ${total} agents in this provider ship a Kiro adapter (\`harnesses/kiro-ide.agent.md\`, \`kiro-cli.agent.json\`).`
|
|
258
493
|
: kiroAvailable === 0
|
|
259
494
|
? `This provider's ${total} agents do not yet ship Kiro adapters — this Power supplies steering content only. Use \`npx vfa-export-agents --platform kiro --provider ${provider}\` from the npm package once Kiro adapters land.`
|
|
260
495
|
: `${kiroAvailable} of ${total} agents in this provider ship a Kiro adapter; the rest provide steering context only.`;
|
|
@@ -273,7 +508,9 @@ function renderPower(provider, cfg) {
|
|
|
273
508
|
"",
|
|
274
509
|
maestroLine,
|
|
275
510
|
"",
|
|
276
|
-
|
|
511
|
+
maestro
|
|
512
|
+
? "Use the maestro as the entry point: classify the task, then dispatch to one specialist or a parallel team of specialists. Never have the maestro itself execute a live mutation."
|
|
513
|
+
: "Reference agents directly from agents/" + provider + "/ without maestro-based routing.",
|
|
277
514
|
"",
|
|
278
515
|
"## Live-guard agents (gate_mode only)",
|
|
279
516
|
"",
|
|
@@ -299,10 +536,114 @@ function renderPower(provider, cfg) {
|
|
|
299
536
|
return frontmatter + body;
|
|
300
537
|
}
|
|
301
538
|
|
|
539
|
+
function renderReadme() {
|
|
540
|
+
const providerKeys = Object.keys(allProviders);
|
|
541
|
+
const count = providerKeys.length;
|
|
542
|
+
const tree = providerKeys
|
|
543
|
+
.map((p, i) => {
|
|
544
|
+
const prefix = i < count - 1 ? "├──" : "└──";
|
|
545
|
+
return `${prefix} vanguard-${p}/POWER.md`;
|
|
546
|
+
})
|
|
547
|
+
.join("\n");
|
|
548
|
+
|
|
549
|
+
return `# \`powers/\` — Kiro Powers
|
|
550
|
+
|
|
551
|
+
This directory holds **${count} Kiro Powers** for \`vanguard-frontier-agentic\`, one
|
|
552
|
+
per cloud/platform/IaC provider. Each Power is a directory containing a
|
|
553
|
+
\`POWER.md\` file with strict-5 frontmatter and steering content.
|
|
554
|
+
|
|
555
|
+
## What's in here
|
|
556
|
+
|
|
557
|
+
\`\`\`
|
|
558
|
+
powers/
|
|
559
|
+
${tree}
|
|
560
|
+
\`\`\`
|
|
561
|
+
|
|
562
|
+
Each \`POWER.md\` declares:
|
|
563
|
+
|
|
564
|
+
- **Frontmatter (strict-5):** \`name\`, \`displayName\`, \`description\` (≤ 3
|
|
565
|
+
sentences), \`keywords\` (specific, non-broad), \`author\`. **No other fields
|
|
566
|
+
permitted** by Kiro spec.
|
|
567
|
+
- **Body steering:** when to engage, routing pattern (\`<provider>-maestro\`),
|
|
568
|
+
live-mutation discipline, provider-specific invariants (e.g. MLPS 2.0 for
|
|
569
|
+
Alibaba/Huawei, Enterprise Project vs IAM scope for Huawei, account-ID
|
|
570
|
+
/region confirmation for AWS).
|
|
571
|
+
|
|
572
|
+
## How users install
|
|
573
|
+
|
|
574
|
+
Kiro Powers don't have a one-command marketplace install — the Powers panel
|
|
575
|
+
is per-Power directory add. Users clone the repo and add each Power they
|
|
576
|
+
need via the Kiro UI:
|
|
577
|
+
|
|
578
|
+
\`\`\`bash
|
|
579
|
+
# 1. Clone the repo
|
|
580
|
+
git clone https://github.com/Raishin/vanguard-frontier-agentic
|
|
581
|
+
cd vanguard-frontier-agentic
|
|
582
|
+
\`\`\`
|
|
583
|
+
|
|
584
|
+
\`\`\`text
|
|
585
|
+
2. In Kiro:
|
|
586
|
+
Open the Powers panel → "Add Custom Power" → "Local Directory"
|
|
587
|
+
Paste the absolute path to the Power(s) you need:
|
|
588
|
+
/absolute/path/to/vanguard-frontier-agentic/powers/vanguard-aws
|
|
589
|
+
/absolute/path/to/vanguard-frontier-agentic/powers/vanguard-kubernetes
|
|
590
|
+
Repeat for each provider you work with.
|
|
591
|
+
\`\`\`
|
|
592
|
+
|
|
593
|
+
## How to update
|
|
594
|
+
|
|
595
|
+
\`\`\`bash
|
|
596
|
+
# Regenerate the ${count} Powers from catalog/agents.json + per-provider config:
|
|
597
|
+
npm run kiro-powers:write
|
|
598
|
+
|
|
599
|
+
# Then verify everything is in sync:
|
|
600
|
+
npm run validate:kiro-powers
|
|
601
|
+
\`\`\`
|
|
602
|
+
|
|
603
|
+
The \`validate\` chain runs \`validate:kiro-powers\` automatically. The
|
|
604
|
+
validator enforces:
|
|
605
|
+
|
|
606
|
+
- strict-5 frontmatter (any extra field fails)
|
|
607
|
+
- lowercase kebab-case names
|
|
608
|
+
- name matches directory name
|
|
609
|
+
- description ≤ 3 sentences (decimal-aware — "MLPS 2.0" doesn't count as a
|
|
610
|
+
sentence break)
|
|
611
|
+
- non-empty keywords list, no broad terms (\`cloud\`, \`devops\`, \`code\`,
|
|
612
|
+
\`agent\`, \`ml\`, etc.) per Kiro's anti-false-activation guidance
|
|
613
|
+
- generator in sync (\`--check\`)
|
|
614
|
+
|
|
615
|
+
## Schema references (official Kiro docs)
|
|
616
|
+
|
|
617
|
+
- **Kiro Powers repo:** <https://github.com/kirodotdev/powers>
|
|
618
|
+
- **POWER.md frontmatter spec:**
|
|
619
|
+
<https://github.com/kirodotdev/powers/blob/main/power-builder/POWER.md>
|
|
620
|
+
- **Interactive power builder:**
|
|
621
|
+
<https://github.com/kirodotdev/powers/blob/main/power-builder/steering/interactive.md>
|
|
622
|
+
- **Testing a power locally:**
|
|
623
|
+
<https://github.com/kirodotdev/powers/blob/main/power-builder/steering/testing.md>
|
|
624
|
+
- **Kiro IDE:** <https://kiro.dev/>
|
|
625
|
+
|
|
626
|
+
## Design notes
|
|
627
|
+
|
|
628
|
+
- **One Power per provider, not one mega-Power** — Kiro docs warn that
|
|
629
|
+
broad keywords trigger false activations across unrelated tasks. One
|
|
630
|
+
narrowly-scoped Power per provider keeps activation precise:
|
|
631
|
+
\`vanguard-alibaba\` activates on Alibaba Cloud work only; \`vanguard-aws\`
|
|
632
|
+
never activates on Azure questions.
|
|
633
|
+
- **Hetzner and Contabo Powers exist** even though their agents don't yet
|
|
634
|
+
ship Kiro adapter files (their \`harnesses: [codex, claude-code]\`). Powers
|
|
635
|
+
are steering-first — the steering content stands alone. When their Kiro
|
|
636
|
+
adapter files land, the Powers will gain agent-routing as well.
|
|
637
|
+
- **No \`version\`, \`repository\`, \`license\`, or \`tags\`** — Kiro spec
|
|
638
|
+
explicitly forbids these fields in frontmatter. The validator fails on
|
|
639
|
+
any extra field.
|
|
640
|
+
`;
|
|
641
|
+
}
|
|
642
|
+
|
|
302
643
|
const errors = [];
|
|
303
644
|
const written = [];
|
|
304
645
|
|
|
305
|
-
for (const [provider, cfg] of Object.entries(
|
|
646
|
+
for (const [provider, cfg] of Object.entries(allProviders)) {
|
|
306
647
|
const dir = join(powersRoot, `vanguard-${provider}`);
|
|
307
648
|
const file = join(dir, "POWER.md");
|
|
308
649
|
const next = renderPower(provider, cfg);
|
|
@@ -322,15 +663,29 @@ for (const [provider, cfg] of Object.entries(PROVIDERS)) {
|
|
|
322
663
|
}
|
|
323
664
|
}
|
|
324
665
|
|
|
666
|
+
// Generate/check README.md
|
|
667
|
+
const readmePath = join(powersRoot, "README.md");
|
|
668
|
+
const nextReadme = renderReadme();
|
|
669
|
+
if (check) {
|
|
670
|
+
if (!existsSync(readmePath)) {
|
|
671
|
+
errors.push(`${relative(repoRoot, readmePath)} is missing`);
|
|
672
|
+
} else if (readFileSync(readmePath, "utf8") !== nextReadme) {
|
|
673
|
+
errors.push(`${relative(repoRoot, readmePath)} is stale; run npm run kiro-powers:write`);
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
writeFileSync(readmePath, nextReadme);
|
|
677
|
+
written.push(relative(repoRoot, readmePath));
|
|
678
|
+
}
|
|
679
|
+
|
|
325
680
|
if (check) {
|
|
326
681
|
if (errors.length) {
|
|
327
682
|
errors.forEach((e) => console.error(`ERROR: ${e}`));
|
|
328
683
|
process.exit(1);
|
|
329
684
|
}
|
|
330
685
|
console.log(
|
|
331
|
-
`OK: ${Object.keys(
|
|
686
|
+
`OK: ${Object.keys(allProviders).length} Kiro Powers are in sync`,
|
|
332
687
|
);
|
|
333
688
|
} else {
|
|
334
|
-
console.log(`OK: wrote ${written.length} Kiro Powers`);
|
|
689
|
+
console.log(`OK: wrote ${written.length} Kiro Powers (+ README.md)`);
|
|
335
690
|
written.forEach((f) => console.log(` ${f}`));
|
|
336
691
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reliable two-stage installer for Vanguard Frontier Agentic on Codex.
|
|
4
|
+
*
|
|
5
|
+
* Stage 1: register/refresh the Codex plugin marketplace.
|
|
6
|
+
* Stage 2: export all Codex-capable agents and companion skills into a Codex home.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
|
|
14
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
const exporter = path.join(repoRoot, "scripts", "export-marketplace-agents.mjs");
|
|
16
|
+
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const opts = {
|
|
19
|
+
marketplace: "Raishin/vanguard-frontier-agentic",
|
|
20
|
+
repo: os.homedir(),
|
|
21
|
+
force: true,
|
|
22
|
+
skipMarketplace: false,
|
|
23
|
+
dryRun: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function usage(exitCode = 0) {
|
|
27
|
+
const out = exitCode === 0 ? console.log : console.error;
|
|
28
|
+
out(`Usage: node scripts/install-codex-home.mjs [options]\n\nOptions:\n --marketplace <source> Codex marketplace source (default: Raishin/vanguard-frontier-agentic)\n --repo <path> Target home/repo path whose .codex folder receives agents/skills (default: $HOME)\n --dry-run Do not write agents/skills; pass --dry-run to exporter\n --skip-marketplace Skip codex plugin marketplace add/upgrade\n --no-force Do not pass --force to exporter\n -h, --help Show this help\n`);
|
|
29
|
+
process.exit(exitCode);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < args.length; i++) {
|
|
33
|
+
const arg = args[i];
|
|
34
|
+
if (arg === "-h" || arg === "--help") usage(0);
|
|
35
|
+
if (arg === "--marketplace") {
|
|
36
|
+
const val = args[++i];
|
|
37
|
+
if (!val || val.startsWith("-")) { console.error("--marketplace requires a non-flag value"); usage(1); }
|
|
38
|
+
opts.marketplace = val;
|
|
39
|
+
} else if (arg === "--repo") {
|
|
40
|
+
const val = args[++i];
|
|
41
|
+
if (!val || val.startsWith("-")) { console.error("--repo requires a non-flag value"); usage(1); }
|
|
42
|
+
opts.repo = val;
|
|
43
|
+
}
|
|
44
|
+
else if (arg === "--dry-run") opts.dryRun = true;
|
|
45
|
+
else if (arg === "--skip-marketplace") opts.skipMarketplace = true;
|
|
46
|
+
else if (arg === "--no-force") opts.force = false;
|
|
47
|
+
else {
|
|
48
|
+
console.error(`Unknown option: ${arg}`);
|
|
49
|
+
usage(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!opts.marketplace) {
|
|
54
|
+
console.error("--marketplace cannot be empty");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
if (!opts.repo) {
|
|
58
|
+
console.error("--repo cannot be empty");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function run(label, command, commandArgs, options = {}) {
|
|
63
|
+
console.error(`\n[${label}] ${command} ${commandArgs.join(" ")}`);
|
|
64
|
+
const result = spawnSync(command, commandArgs, {
|
|
65
|
+
cwd: repoRoot,
|
|
66
|
+
stdio: "inherit",
|
|
67
|
+
...options,
|
|
68
|
+
});
|
|
69
|
+
if (result.error) {
|
|
70
|
+
console.error(`[${label}] failed to start: ${result.error.message}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
if (result.status !== 0) {
|
|
74
|
+
console.error(`[${label}] exited ${result.status}`);
|
|
75
|
+
process.exit(result.status ?? 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!opts.skipMarketplace) {
|
|
80
|
+
run("marketplace-add", "codex", ["plugin", "marketplace", "add", opts.marketplace]);
|
|
81
|
+
const marketplaceName = opts.marketplace
|
|
82
|
+
.split("/").pop()
|
|
83
|
+
?.replace(/\.git$/, "")
|
|
84
|
+
?.replace(/@.+$/, "");
|
|
85
|
+
if (marketplaceName) {
|
|
86
|
+
run("marketplace-upgrade", "codex", ["plugin", "marketplace", "upgrade", marketplaceName]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const exportArgs = ["--platform", "codex", "--all", "--repo", opts.repo];
|
|
91
|
+
if (opts.force) exportArgs.push("--force");
|
|
92
|
+
if (opts.dryRun) exportArgs.push("--dry-run");
|
|
93
|
+
run("export-agents-and-skills", process.execPath, [exporter, ...exportArgs]);
|
|
94
|
+
|
|
95
|
+
console.error("\nOK: two-stage Codex install completed");
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Optional E2E check for the real Codex marketplace-add command.
|
|
4
|
+
*
|
|
5
|
+
* This test is intentionally opt-in because it runs the installed `codex` CLI
|
|
6
|
+
* and may hit the network when CODEX_PLUGIN_MARKETPLACE_SOURCE is a GitHub
|
|
7
|
+
* shorthand. It uses an isolated CODEX_HOME and never writes to ~/.codex.
|
|
8
|
+
*
|
|
9
|
+
* What it proves:
|
|
10
|
+
* - `codex plugin marketplace add <source>` exits successfully.
|
|
11
|
+
* - Codex tracks the marketplace in the isolated CODEX_HOME/config.toml.
|
|
12
|
+
* - Codex materializes the marketplace source under CODEX_HOME/.tmp/marketplaces.
|
|
13
|
+
* - The materialized marketplace contains the repo's Codex marketplace and plugin manifests.
|
|
14
|
+
*
|
|
15
|
+
* What it does NOT prove:
|
|
16
|
+
* - It does not prove a plugin was installed into CODEX_HOME/plugins/cache/... .
|
|
17
|
+
* OpenAI docs describe that as plugin installation through a marketplace;
|
|
18
|
+
* the current CLI command under test is marketplace-add, not plugin-install.
|
|
19
|
+
*
|
|
20
|
+
* Run:
|
|
21
|
+
* RUN_CODEX_PLUGIN_MARKETPLACE_E2E=1 node tests/test-codex-plugin-marketplace-install.test.mjs
|
|
22
|
+
*
|
|
23
|
+
* Optional override:
|
|
24
|
+
* CODEX_PLUGIN_MARKETPLACE_SOURCE=Raishin/vanguard-frontier-agentic@main \
|
|
25
|
+
* RUN_CODEX_PLUGIN_MARKETPLACE_E2E=1 node tests/test-codex-plugin-marketplace-install.test.mjs
|
|
26
|
+
*
|
|
27
|
+
* Strict cache assertion, expected to fail for marketplace-add-only on the
|
|
28
|
+
* current Codex CLI unless a separate plugin install path populates cache:
|
|
29
|
+
* EXPECT_CODEX_PLUGIN_CACHE=1 RUN_CODEX_PLUGIN_MARKETPLACE_E2E=1 \
|
|
30
|
+
* node tests/test-codex-plugin-marketplace-install.test.mjs
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { spawnSync } from "node:child_process";
|
|
34
|
+
import fs from "node:fs";
|
|
35
|
+
import os from "node:os";
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
|
|
38
|
+
const enabled = process.env.RUN_CODEX_PLUGIN_MARKETPLACE_E2E === "1";
|
|
39
|
+
if (!enabled) {
|
|
40
|
+
console.log("SKIP codex marketplace E2E; set RUN_CODEX_PLUGIN_MARKETPLACE_E2E=1 to run it");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const source = process.env.CODEX_PLUGIN_MARKETPLACE_SOURCE || "Raishin/vanguard-frontier-agentic";
|
|
45
|
+
const marketplaceName = process.env.CODEX_PLUGIN_MARKETPLACE_NAME || "vanguard-frontier-agentic";
|
|
46
|
+
const expectPluginCache = process.env.EXPECT_CODEX_PLUGIN_CACHE === "1";
|
|
47
|
+
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), "vfa-codex-home-"));
|
|
48
|
+
|
|
49
|
+
let failures = 0;
|
|
50
|
+
const ok = (msg) => console.log(`OK ${msg}`);
|
|
51
|
+
const fail = (msg) => {
|
|
52
|
+
console.log(`FAIL ${msg}`);
|
|
53
|
+
failures += 1;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function exists(rel) {
|
|
57
|
+
return fs.existsSync(path.join(codexHome, rel));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = spawnSync(
|
|
62
|
+
"codex",
|
|
63
|
+
["plugin", "marketplace", "add", source],
|
|
64
|
+
{
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
env: {
|
|
67
|
+
...process.env,
|
|
68
|
+
CODEX_HOME: codexHome,
|
|
69
|
+
},
|
|
70
|
+
timeout: 120000,
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (result.error?.code === "ENOENT") {
|
|
75
|
+
console.log("SKIP codex marketplace E2E; `codex` executable not found on PATH");
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
if (result.signal === "SIGTERM") {
|
|
79
|
+
fail("codex marketplace add timed out after 120s");
|
|
80
|
+
}
|
|
81
|
+
if (result.status === 0) {
|
|
82
|
+
ok(`codex plugin marketplace add ${source} exits 0`);
|
|
83
|
+
} else {
|
|
84
|
+
fail(`codex marketplace add exited ${result.status}; stderr=${(result.stderr || "").slice(0, 1000)}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const configPath = path.join(codexHome, "config.toml");
|
|
88
|
+
const config = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
89
|
+
if (config.includes(`[marketplaces.${marketplaceName}]`)) {
|
|
90
|
+
ok(`config.toml tracks marketplace ${marketplaceName}`);
|
|
91
|
+
} else {
|
|
92
|
+
fail(`config.toml missing [marketplaces.${marketplaceName}]`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const installedRoot = path.join(codexHome, ".tmp", "marketplaces", marketplaceName);
|
|
96
|
+
if (fs.existsSync(installedRoot)) {
|
|
97
|
+
ok(`marketplace source materialized at ${installedRoot}`);
|
|
98
|
+
} else {
|
|
99
|
+
fail(`marketplace source missing at ${installedRoot}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const requiredFiles = [
|
|
103
|
+
`.tmp/marketplaces/${marketplaceName}/.agents/plugins/marketplace.json`,
|
|
104
|
+
`.tmp/marketplaces/${marketplaceName}/plugins/vanguard-frontier-agentic/.codex-plugin/plugin.json`,
|
|
105
|
+
`.tmp/marketplaces/${marketplaceName}/plugins/cross-platform-agent-template/.codex-plugin/plugin.json`,
|
|
106
|
+
];
|
|
107
|
+
for (const rel of requiredFiles) {
|
|
108
|
+
if (exists(rel)) ok(`${rel} exists`);
|
|
109
|
+
else fail(`${rel} missing`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const cacheRoot = path.join(codexHome, "plugins", "cache", marketplaceName);
|
|
113
|
+
if (fs.existsSync(cacheRoot)) {
|
|
114
|
+
ok(`plugin cache exists at ${cacheRoot}`);
|
|
115
|
+
} else if (expectPluginCache) {
|
|
116
|
+
fail(`plugin cache missing at ${cacheRoot}`);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`INFO plugin cache not created by marketplace-add alone: ${cacheRoot}`);
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
if (process.env.KEEP_CODEX_MARKETPLACE_E2E_HOME !== "1") {
|
|
122
|
+
fs.rmSync(codexHome, { recursive: true, force: true });
|
|
123
|
+
} else {
|
|
124
|
+
console.log(`INFO kept isolated CODEX_HOME at ${codexHome}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (failures > 0) {
|
|
129
|
+
console.error(`\n${failures} check(s) failed`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
console.log("\nOK: codex marketplace add E2E checks passed");
|