@h9-foundry/agentforge-cli 0.9.0 → 0.10.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/dist/.tsbuildinfo +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +444 -19
- package/dist/index.js.map +1 -1
- package/dist/internal/builtin-agents.d.ts.map +1 -1
- package/dist/internal/builtin-agents.js +1351 -37
- package/dist/internal/builtin-agents.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { buildAuditBundle, createAuditEntry, renderAuditBundleMarkdown } from "@
|
|
|
6
6
|
import { createWorkflowState, findWorkspaceRoot } from "@h9-foundry/agentforge-context-engine";
|
|
7
7
|
import { createPolicyEngine, loadPolicyDocument, resolvePolicy } from "@h9-foundry/agentforge-policy-engine";
|
|
8
8
|
import { runWorkflow } from "@h9-foundry/agentforge-runtime";
|
|
9
|
-
import { agentforgeConfigSchema, auditBundleSchema, benchmarkArtifactSchema, designArtifactSchema, designRequestSchema, evalArtifactSchema, evalFixtureCorpusSchema, implementationRequestSchema, incidentRequestSchema, maintenanceRequestSchema, planningArtifactSchema, planningRequestSchema, qaRequestSchema, releaseRequestSchema, schemaFixtures, securityRequestSchema, workflowDefinitionSchema } from "@h9-foundry/agentforge-schemas";
|
|
9
|
+
import { agentforgeConfigSchema, auditBundleSchema, benchmarkArtifactSchema, designArtifactSchema, designRequestSchema, deploymentRequestSchema, evalArtifactSchema, evalFixtureCorpusSchema, implementationRequestSchema, incidentRequestSchema, maintenanceRequestSchema, pipelineRequestSchema, planningArtifactSchema, planningRequestSchema, qaRequestSchema, releaseRequestSchema, schemaFixtures, securityRequestSchema, workflowDefinitionSchema } from "@h9-foundry/agentforge-schemas";
|
|
10
10
|
import { createBuiltinAdapters } from "./internal/builtin-adapters.js";
|
|
11
11
|
import { createBuiltinAgentRegistry } from "./internal/builtin-agents.js";
|
|
12
12
|
import { LocalPluginRegistry } from "./internal/local-plugin-registry.js";
|
|
@@ -239,7 +239,7 @@ description: Validate a bounded release-readiness request while keeping trusted
|
|
|
239
239
|
trigger: manual
|
|
240
240
|
catalog:
|
|
241
241
|
domain: release
|
|
242
|
-
supportLevel:
|
|
242
|
+
supportLevel: official
|
|
243
243
|
maturity: mvp
|
|
244
244
|
trustScope: official-core-only
|
|
245
245
|
nodes:
|
|
@@ -259,6 +259,58 @@ nodes:
|
|
|
259
259
|
kind: report
|
|
260
260
|
outputs_to: reports.final
|
|
261
261
|
`;
|
|
262
|
+
const pipelineWorkflowTemplate = `version: 1
|
|
263
|
+
name: pipeline-evidence-review
|
|
264
|
+
description: Review bounded local pipeline evidence through the shared CI model without assuming a release target.
|
|
265
|
+
trigger: manual
|
|
266
|
+
catalog:
|
|
267
|
+
domain: release
|
|
268
|
+
supportLevel: official
|
|
269
|
+
maturity: mvp
|
|
270
|
+
trustScope: official-core-only
|
|
271
|
+
nodes:
|
|
272
|
+
- id: intake
|
|
273
|
+
kind: deterministic
|
|
274
|
+
agent: pipeline-intake
|
|
275
|
+
outputs_to: agentResults.intake
|
|
276
|
+
- id: evidence
|
|
277
|
+
kind: deterministic
|
|
278
|
+
agent: pipeline-evidence-normalizer
|
|
279
|
+
outputs_to: agentResults.evidence
|
|
280
|
+
- id: pipeline
|
|
281
|
+
kind: reasoning
|
|
282
|
+
agent: pipeline-analyst
|
|
283
|
+
outputs_to: agentResults.pipeline
|
|
284
|
+
- id: report
|
|
285
|
+
kind: report
|
|
286
|
+
outputs_to: reports.final
|
|
287
|
+
`;
|
|
288
|
+
const deploymentGateWorkflowTemplate = `version: 1
|
|
289
|
+
name: deployment-gate-review
|
|
290
|
+
description: Review a bounded deployment candidate using shared CI evidence and referenced lifecycle artifacts.
|
|
291
|
+
trigger: manual
|
|
292
|
+
catalog:
|
|
293
|
+
domain: release
|
|
294
|
+
supportLevel: official
|
|
295
|
+
maturity: mvp
|
|
296
|
+
trustScope: official-core-only
|
|
297
|
+
nodes:
|
|
298
|
+
- id: intake
|
|
299
|
+
kind: deterministic
|
|
300
|
+
agent: deployment-gate-intake
|
|
301
|
+
outputs_to: agentResults.intake
|
|
302
|
+
- id: evidence
|
|
303
|
+
kind: deterministic
|
|
304
|
+
agent: deployment-gate-evidence-normalizer
|
|
305
|
+
outputs_to: agentResults.evidence
|
|
306
|
+
- id: deployment
|
|
307
|
+
kind: reasoning
|
|
308
|
+
agent: deployment-gate-analyst
|
|
309
|
+
outputs_to: agentResults.deployment
|
|
310
|
+
- id: report
|
|
311
|
+
kind: report
|
|
312
|
+
outputs_to: reports.final
|
|
313
|
+
`;
|
|
262
314
|
const incidentWorkflowTemplate = `version: 1
|
|
263
315
|
name: incident-handoff
|
|
264
316
|
description: Validate staged incident evidence while keeping the default path local, read-only, and explicit.
|
|
@@ -328,24 +380,90 @@ function runGit(root, args) {
|
|
|
328
380
|
return "";
|
|
329
381
|
}
|
|
330
382
|
}
|
|
331
|
-
function
|
|
383
|
+
function inferScmPlatform(host) {
|
|
384
|
+
const normalizedHost = host.toLowerCase();
|
|
385
|
+
if (normalizedHost.includes("github")) {
|
|
386
|
+
return "github";
|
|
387
|
+
}
|
|
388
|
+
if (normalizedHost.includes("gitlab")) {
|
|
389
|
+
return "gitlab";
|
|
390
|
+
}
|
|
391
|
+
return "generic";
|
|
392
|
+
}
|
|
393
|
+
function parseScmRepositoryUrl(value) {
|
|
332
394
|
const trimmed = value.trim();
|
|
333
|
-
const sshMatch = trimmed.match(/^git@([^:]+):(
|
|
395
|
+
const sshMatch = trimmed.match(/^git@([^:]+):(.+?)(?:\.git)?$/i);
|
|
334
396
|
if (sshMatch) {
|
|
397
|
+
const host = sshMatch[1].toLowerCase();
|
|
398
|
+
const pathSegments = sshMatch[2].split("/").filter(Boolean);
|
|
399
|
+
if (pathSegments.length < 2) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const repo = pathSegments[pathSegments.length - 1] ?? "";
|
|
403
|
+
const namespace = pathSegments.slice(0, -1).join("/");
|
|
404
|
+
const platform = inferScmPlatform(host);
|
|
405
|
+
if (platform === "github") {
|
|
406
|
+
return {
|
|
407
|
+
platform,
|
|
408
|
+
host,
|
|
409
|
+
owner: namespace,
|
|
410
|
+
repo
|
|
411
|
+
};
|
|
412
|
+
}
|
|
335
413
|
return {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
414
|
+
platform,
|
|
415
|
+
host,
|
|
416
|
+
namespace,
|
|
417
|
+
repo
|
|
339
418
|
};
|
|
340
419
|
}
|
|
341
|
-
const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/(
|
|
420
|
+
const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?(?:\/)?$/i);
|
|
342
421
|
if (!httpsMatch) {
|
|
343
422
|
return undefined;
|
|
344
423
|
}
|
|
424
|
+
const host = httpsMatch[1].toLowerCase();
|
|
425
|
+
const pathSegments = httpsMatch[2].split("/").filter(Boolean);
|
|
426
|
+
if (pathSegments.length < 2) {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
const repo = pathSegments[pathSegments.length - 1] ?? "";
|
|
430
|
+
const namespace = pathSegments.slice(0, -1).join("/");
|
|
431
|
+
const platform = inferScmPlatform(host);
|
|
432
|
+
if (platform === "github") {
|
|
433
|
+
return {
|
|
434
|
+
platform,
|
|
435
|
+
host,
|
|
436
|
+
owner: namespace,
|
|
437
|
+
repo
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
platform,
|
|
442
|
+
host,
|
|
443
|
+
namespace,
|
|
444
|
+
repo
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function parseGitHubRepositoryUrl(value) {
|
|
448
|
+
const parsed = parseScmRepositoryUrl(value);
|
|
449
|
+
if (!parsed || parsed.platform !== "github") {
|
|
450
|
+
return undefined;
|
|
451
|
+
}
|
|
345
452
|
return {
|
|
346
|
-
host:
|
|
347
|
-
owner:
|
|
348
|
-
repo:
|
|
453
|
+
host: parsed.host,
|
|
454
|
+
owner: parsed.owner,
|
|
455
|
+
repo: parsed.repo
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function parseGitLabRepositoryUrl(value) {
|
|
459
|
+
const parsed = parseScmRepositoryUrl(value);
|
|
460
|
+
if (!parsed || parsed.platform !== "gitlab") {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
host: parsed.host,
|
|
465
|
+
namespace: parsed.namespace,
|
|
466
|
+
repo: parsed.repo
|
|
349
467
|
};
|
|
350
468
|
}
|
|
351
469
|
function inferGitHubRepoContext(root) {
|
|
@@ -371,6 +489,60 @@ function inferGitHubRepoContext(root) {
|
|
|
371
489
|
const remoteUrl = runGit(root, ["config", "--get", "remote.origin.url"]);
|
|
372
490
|
return remoteUrl ? parseGitHubRepositoryUrl(remoteUrl) : undefined;
|
|
373
491
|
}
|
|
492
|
+
function inferGitLabRepoContext(root) {
|
|
493
|
+
const packageJsonPath = join(root, "package.json");
|
|
494
|
+
if (existsSync(packageJsonPath)) {
|
|
495
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
496
|
+
if (isRecord(parsed)) {
|
|
497
|
+
const repository = parsed.repository;
|
|
498
|
+
if (typeof repository === "string") {
|
|
499
|
+
const context = parseGitLabRepositoryUrl(repository);
|
|
500
|
+
if (context) {
|
|
501
|
+
return context;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (isRecord(repository) && typeof repository.url === "string") {
|
|
505
|
+
const context = parseGitLabRepositoryUrl(repository.url);
|
|
506
|
+
if (context) {
|
|
507
|
+
return context;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const remoteUrl = runGit(root, ["config", "--get", "remote.origin.url"]);
|
|
513
|
+
return remoteUrl ? parseGitLabRepositoryUrl(remoteUrl) : undefined;
|
|
514
|
+
}
|
|
515
|
+
function inferScmRepoContext(root) {
|
|
516
|
+
const gitHubContext = inferGitHubRepoContext(root);
|
|
517
|
+
if (gitHubContext) {
|
|
518
|
+
return { platform: "github", ...gitHubContext };
|
|
519
|
+
}
|
|
520
|
+
const gitLabContext = inferGitLabRepoContext(root);
|
|
521
|
+
if (gitLabContext) {
|
|
522
|
+
return { platform: "gitlab", ...gitLabContext };
|
|
523
|
+
}
|
|
524
|
+
const packageJsonPath = join(root, "package.json");
|
|
525
|
+
if (existsSync(packageJsonPath)) {
|
|
526
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
527
|
+
if (isRecord(parsed)) {
|
|
528
|
+
const repository = parsed.repository;
|
|
529
|
+
if (typeof repository === "string") {
|
|
530
|
+
const context = parseScmRepositoryUrl(repository);
|
|
531
|
+
if (context) {
|
|
532
|
+
return context;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (isRecord(repository) && typeof repository.url === "string") {
|
|
536
|
+
const context = parseScmRepositoryUrl(repository.url);
|
|
537
|
+
if (context) {
|
|
538
|
+
return context;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
const remoteUrl = runGit(root, ["config", "--get", "remote.origin.url"]);
|
|
544
|
+
return remoteUrl ? parseScmRepositoryUrl(remoteUrl) : undefined;
|
|
545
|
+
}
|
|
374
546
|
function normalizeGitHubReference(rawValue, repoContext) {
|
|
375
547
|
const raw = rawValue.trim();
|
|
376
548
|
if (!raw) {
|
|
@@ -426,6 +598,86 @@ function normalizeGitHubReferences(rawValues, repoContext) {
|
|
|
426
598
|
}
|
|
427
599
|
return normalized;
|
|
428
600
|
}
|
|
601
|
+
function normalizeGitLabReference(rawValue, repoContext) {
|
|
602
|
+
const raw = rawValue.trim();
|
|
603
|
+
if (!raw) {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
606
|
+
const fromParts = (context, kind, number) => ({
|
|
607
|
+
platform: "gitlab",
|
|
608
|
+
host: context.host,
|
|
609
|
+
namespace: context.namespace,
|
|
610
|
+
repo: context.repo,
|
|
611
|
+
kind,
|
|
612
|
+
identifier: `${number}`,
|
|
613
|
+
number,
|
|
614
|
+
canonical: kind === "issue"
|
|
615
|
+
? `${context.host}/${context.namespace}/${context.repo}#${number}`
|
|
616
|
+
: `${context.host}/${context.namespace}/${context.repo}!${number}`,
|
|
617
|
+
url: kind === "issue"
|
|
618
|
+
? `https://${context.host}/${context.namespace}/${context.repo}/-/issues/${number}`
|
|
619
|
+
: `https://${context.host}/${context.namespace}/${context.repo}/-/merge_requests/${number}`,
|
|
620
|
+
source: raw
|
|
621
|
+
});
|
|
622
|
+
const urlMatch = raw.match(/^https?:\/\/([^/]+)\/(.+?)\/-\/(issues|merge_requests)\/(\d+)(?:\/)?$/i);
|
|
623
|
+
if (urlMatch) {
|
|
624
|
+
const pathSegments = urlMatch[2].split("/").filter(Boolean);
|
|
625
|
+
if (pathSegments.length < 2) {
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
const repo = pathSegments[pathSegments.length - 1] ?? "";
|
|
629
|
+
const namespace = pathSegments.slice(0, -1).join("/");
|
|
630
|
+
return fromParts({ host: urlMatch[1].toLowerCase(), namespace, repo }, urlMatch[3].toLowerCase() === "merge_requests" ? "merge_request" : "issue", Number.parseInt(urlMatch[4], 10));
|
|
631
|
+
}
|
|
632
|
+
const shortIssueMatch = raw.match(/^#(\d+)$/);
|
|
633
|
+
if (shortIssueMatch && repoContext) {
|
|
634
|
+
return fromParts(repoContext, "issue", Number.parseInt(shortIssueMatch[1], 10));
|
|
635
|
+
}
|
|
636
|
+
const shortMergeRequestMatch = raw.match(/^!(\d+)$/);
|
|
637
|
+
if (shortMergeRequestMatch && repoContext) {
|
|
638
|
+
return fromParts(repoContext, "merge_request", Number.parseInt(shortMergeRequestMatch[1], 10));
|
|
639
|
+
}
|
|
640
|
+
return undefined;
|
|
641
|
+
}
|
|
642
|
+
function normalizeScmReference(rawValue, repoContext) {
|
|
643
|
+
const raw = rawValue.trim();
|
|
644
|
+
if (!raw) {
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
const gitHubRef = normalizeGitHubReference(raw, repoContext?.platform === "github"
|
|
648
|
+
? { host: repoContext.host, owner: repoContext.owner, repo: repoContext.repo }
|
|
649
|
+
: undefined);
|
|
650
|
+
if (gitHubRef) {
|
|
651
|
+
return {
|
|
652
|
+
platform: "github",
|
|
653
|
+
host: gitHubRef.host,
|
|
654
|
+
namespace: gitHubRef.owner,
|
|
655
|
+
repo: gitHubRef.repo,
|
|
656
|
+
kind: gitHubRef.kind,
|
|
657
|
+
identifier: `${gitHubRef.number}`,
|
|
658
|
+
number: gitHubRef.number,
|
|
659
|
+
canonical: `${gitHubRef.host}/${gitHubRef.owner}/${gitHubRef.repo}${gitHubRef.kind === "issue" ? `#${gitHubRef.number}` : `/pull/${gitHubRef.number}`}`,
|
|
660
|
+
url: gitHubRef.url,
|
|
661
|
+
source: gitHubRef.source
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
return normalizeGitLabReference(raw, repoContext?.platform === "gitlab"
|
|
665
|
+
? { host: repoContext.host, namespace: repoContext.namespace, repo: repoContext.repo }
|
|
666
|
+
: undefined);
|
|
667
|
+
}
|
|
668
|
+
function normalizeScmReferences(rawValues, repoContext) {
|
|
669
|
+
const seen = new Set();
|
|
670
|
+
const normalized = [];
|
|
671
|
+
for (const rawValue of rawValues) {
|
|
672
|
+
const scmRef = normalizeScmReference(rawValue, repoContext);
|
|
673
|
+
if (!scmRef || seen.has(scmRef.canonical)) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
seen.add(scmRef.canonical);
|
|
677
|
+
normalized.push(scmRef);
|
|
678
|
+
}
|
|
679
|
+
return normalized;
|
|
680
|
+
}
|
|
429
681
|
export function mapWorkflowRunStatusToGitHubStatus(workflow, localRunStatus) {
|
|
430
682
|
if (localRunStatus === "success") {
|
|
431
683
|
return {
|
|
@@ -544,6 +796,35 @@ function validateReleaseRequestCompleteness(request) {
|
|
|
544
796
|
}
|
|
545
797
|
return request;
|
|
546
798
|
}
|
|
799
|
+
function validatePipelineRequestCompleteness(request) {
|
|
800
|
+
const evidenceSignalCount = request.evidenceSources.length + request.qaReportRefs.length + request.securityReportRefs.length + request.releaseReportRefs.length;
|
|
801
|
+
if (evidenceSignalCount === 0) {
|
|
802
|
+
throw new Error("Pipeline request is underspecified. Add at least one of evidenceSources, qaReportRefs, securityReportRefs, or releaseReportRefs.");
|
|
803
|
+
}
|
|
804
|
+
return request;
|
|
805
|
+
}
|
|
806
|
+
function validateDeploymentRequestCompleteness(request) {
|
|
807
|
+
const evidenceSignalCount = request.evidenceSources.length +
|
|
808
|
+
request.qaReportRefs.length +
|
|
809
|
+
request.securityReportRefs.length +
|
|
810
|
+
request.releaseReportRefs.length +
|
|
811
|
+
request.pipelineReportRefs.length;
|
|
812
|
+
if (evidenceSignalCount === 0) {
|
|
813
|
+
throw new Error("Deployment request is underspecified. Add at least one of evidenceSources, qaReportRefs, securityReportRefs, releaseReportRefs, or pipelineReportRefs.");
|
|
814
|
+
}
|
|
815
|
+
return request;
|
|
816
|
+
}
|
|
817
|
+
function addSourceReferences(refs, incoming) {
|
|
818
|
+
for (const issueRef of incoming.issueRefs) {
|
|
819
|
+
refs.issueRefs.add(issueRef);
|
|
820
|
+
}
|
|
821
|
+
for (const scmRef of incoming.scmRefs) {
|
|
822
|
+
refs.scmRefMap.set(scmRef.canonical, scmRef);
|
|
823
|
+
}
|
|
824
|
+
for (const githubRef of incoming.githubRefs) {
|
|
825
|
+
refs.githubRefMap.set(githubRef.canonical, githubRef);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
547
828
|
function loadLifecycleArtifactKinds(root, bundleRef) {
|
|
548
829
|
const bundlePath = join(root, bundleRef);
|
|
549
830
|
if (!existsSync(bundlePath)) {
|
|
@@ -558,22 +839,31 @@ function loadLifecycleArtifactSourceReferences(root, bundleRef) {
|
|
|
558
839
|
throw new Error(`Referenced bundle not found: ${bundleRef}`);
|
|
559
840
|
}
|
|
560
841
|
const bundle = auditBundleSchema.parse(JSON.parse(readFileSync(bundlePath, "utf8")));
|
|
561
|
-
const
|
|
842
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
843
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
562
844
|
const issueRefs = new Set();
|
|
845
|
+
const scmRefs = new Map();
|
|
563
846
|
const githubRefs = new Map();
|
|
564
847
|
for (const artifact of bundle.lifecycleArtifacts) {
|
|
565
848
|
for (const issueRef of artifact.source.issueRefs) {
|
|
566
849
|
issueRefs.add(issueRef);
|
|
567
850
|
}
|
|
851
|
+
for (const scmRef of artifact.source.scmRefs ?? []) {
|
|
852
|
+
scmRefs.set(scmRef.canonical, scmRef);
|
|
853
|
+
}
|
|
568
854
|
for (const githubRef of artifact.source.githubRefs ?? []) {
|
|
569
855
|
githubRefs.set(githubRef.canonical, githubRef);
|
|
570
856
|
}
|
|
571
|
-
for (const
|
|
857
|
+
for (const scmRef of normalizeScmReferences(artifact.source.issueRefs, scmRepoContext)) {
|
|
858
|
+
scmRefs.set(scmRef.canonical, scmRef);
|
|
859
|
+
}
|
|
860
|
+
for (const githubRef of normalizeGitHubReferences(artifact.source.issueRefs, gitHubRepoContext)) {
|
|
572
861
|
githubRefs.set(githubRef.canonical, githubRef);
|
|
573
862
|
}
|
|
574
863
|
}
|
|
575
864
|
return {
|
|
576
865
|
issueRefs: [...issueRefs],
|
|
866
|
+
scmRefs: [...scmRefs.values()],
|
|
577
867
|
githubRefs: [...githubRefs.values()]
|
|
578
868
|
};
|
|
579
869
|
}
|
|
@@ -584,9 +874,11 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
584
874
|
const requestPath = ".agentops/requests/planning.yaml";
|
|
585
875
|
ensureReadablePath(policyEngine, requestPath, "planning request");
|
|
586
876
|
const planningRequest = validatePlanningRequestCompleteness(readYamlFile(join(root, requestPath), planningRequestSchema, "planning request"));
|
|
877
|
+
const planningScmRefs = normalizeScmReferences(planningRequest.issueRefs, inferScmRepoContext(root));
|
|
587
878
|
const planningGithubRefs = normalizeGitHubReferences(planningRequest.issueRefs, inferGitHubRepoContext(root));
|
|
588
879
|
return {
|
|
589
880
|
planningRequest,
|
|
881
|
+
planningScmRefs,
|
|
590
882
|
planningGithubRefs,
|
|
591
883
|
requestFile: requestPath
|
|
592
884
|
};
|
|
@@ -628,10 +920,11 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
628
920
|
}
|
|
629
921
|
const referencedSourceRefs = qaRequest.targetRef.endsWith("bundle.json")
|
|
630
922
|
? loadLifecycleArtifactSourceReferences(root, qaRequest.targetRef)
|
|
631
|
-
: { issueRefs: [], githubRefs: [] };
|
|
923
|
+
: { issueRefs: [], scmRefs: [], githubRefs: [] };
|
|
632
924
|
return {
|
|
633
925
|
qaRequest: qaRequest,
|
|
634
926
|
qaIssueRefs: referencedSourceRefs.issueRefs,
|
|
927
|
+
qaScmRefs: referencedSourceRefs.scmRefs,
|
|
635
928
|
qaGithubRefs: referencedSourceRefs.githubRefs,
|
|
636
929
|
requestFile: requestPath
|
|
637
930
|
};
|
|
@@ -656,20 +949,68 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
656
949
|
}
|
|
657
950
|
const referencedSourceRefs = securityRequest.targetRef.endsWith("bundle.json")
|
|
658
951
|
? loadLifecycleArtifactSourceReferences(root, securityRequest.targetRef)
|
|
659
|
-
: { issueRefs: [], githubRefs: [] };
|
|
952
|
+
: { issueRefs: [], scmRefs: [], githubRefs: [] };
|
|
660
953
|
return {
|
|
661
954
|
securityRequest: securityRequest,
|
|
662
955
|
securityTargetArtifactKinds: referencedArtifactKinds,
|
|
663
956
|
securityIssueRefs: referencedSourceRefs.issueRefs,
|
|
957
|
+
securityScmRefs: referencedSourceRefs.scmRefs,
|
|
664
958
|
securityGithubRefs: referencedSourceRefs.githubRefs,
|
|
665
959
|
requestFile: requestPath
|
|
666
960
|
};
|
|
667
961
|
}
|
|
962
|
+
if (workflow.name === "pipeline-evidence-review") {
|
|
963
|
+
const requestPath = ".agentops/requests/pipeline.yaml";
|
|
964
|
+
ensureReadablePath(policyEngine, requestPath, "pipeline request");
|
|
965
|
+
const pipelineRequest = validatePipelineRequestCompleteness(readYamlFile(join(root, requestPath), pipelineRequestSchema, "pipeline request"));
|
|
966
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
967
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
968
|
+
const pipelineRefs = {
|
|
969
|
+
issueRefs: new Set(pipelineRequest.issueRefs),
|
|
970
|
+
scmRefMap: new Map(),
|
|
971
|
+
githubRefMap: new Map()
|
|
972
|
+
};
|
|
973
|
+
for (const scmRef of normalizeScmReferences(pipelineRequest.issueRefs, scmRepoContext)) {
|
|
974
|
+
pipelineRefs.scmRefMap.set(scmRef.canonical, scmRef);
|
|
975
|
+
}
|
|
976
|
+
for (const githubRef of normalizeGitHubReferences(pipelineRequest.issueRefs, gitHubRepoContext)) {
|
|
977
|
+
pipelineRefs.githubRefMap.set(githubRef.canonical, githubRef);
|
|
978
|
+
}
|
|
979
|
+
for (const qaReportRef of pipelineRequest.qaReportRefs) {
|
|
980
|
+
ensureReadablePath(policyEngine, qaReportRef, "QA report reference");
|
|
981
|
+
ensureBundleContainsArtifactKind(root, qaReportRef, "qa-report", "QA report reference");
|
|
982
|
+
addSourceReferences(pipelineRefs, loadLifecycleArtifactSourceReferences(root, qaReportRef));
|
|
983
|
+
}
|
|
984
|
+
for (const securityReportRef of pipelineRequest.securityReportRefs) {
|
|
985
|
+
ensureReadablePath(policyEngine, securityReportRef, "security report reference");
|
|
986
|
+
ensureBundleContainsArtifactKind(root, securityReportRef, "security-report", "security report reference");
|
|
987
|
+
addSourceReferences(pipelineRefs, loadLifecycleArtifactSourceReferences(root, securityReportRef));
|
|
988
|
+
}
|
|
989
|
+
for (const releaseReportRef of pipelineRequest.releaseReportRefs) {
|
|
990
|
+
ensureReadablePath(policyEngine, releaseReportRef, "release report reference");
|
|
991
|
+
ensureBundleContainsArtifactKind(root, releaseReportRef, "release-report", "release report reference");
|
|
992
|
+
addSourceReferences(pipelineRefs, loadLifecycleArtifactSourceReferences(root, releaseReportRef));
|
|
993
|
+
}
|
|
994
|
+
for (const evidenceSource of pipelineRequest.evidenceSources) {
|
|
995
|
+
ensureReadablePath(policyEngine, evidenceSource, "pipeline evidence source");
|
|
996
|
+
if (!existsSync(join(root, evidenceSource))) {
|
|
997
|
+
throw new Error(`Pipeline evidence source not found: ${evidenceSource}`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return {
|
|
1001
|
+
pipelineRequest: pipelineRequest,
|
|
1002
|
+
pipelineIssueRefs: [...pipelineRefs.issueRefs],
|
|
1003
|
+
pipelineScmRefs: [...pipelineRefs.scmRefMap.values()],
|
|
1004
|
+
pipelineGithubRefs: [...pipelineRefs.githubRefMap.values()],
|
|
1005
|
+
requestFile: requestPath
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
668
1008
|
if (workflow.name === "release-readiness") {
|
|
669
1009
|
const requestPath = ".agentops/requests/release.yaml";
|
|
670
1010
|
ensureReadablePath(policyEngine, requestPath, "release request");
|
|
671
1011
|
const releaseRequest = validateReleaseRequestCompleteness(readYamlFile(join(root, requestPath), releaseRequestSchema, "release request"));
|
|
672
1012
|
const releaseIssueRefs = new Set();
|
|
1013
|
+
const releaseScmRefMap = new Map();
|
|
673
1014
|
const releaseGithubRefMap = new Map();
|
|
674
1015
|
for (const qaReportRef of releaseRequest.qaReportRefs) {
|
|
675
1016
|
ensureReadablePath(policyEngine, qaReportRef, "QA report reference");
|
|
@@ -678,6 +1019,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
678
1019
|
for (const issueRef of refs.issueRefs) {
|
|
679
1020
|
releaseIssueRefs.add(issueRef);
|
|
680
1021
|
}
|
|
1022
|
+
for (const scmRef of refs.scmRefs) {
|
|
1023
|
+
releaseScmRefMap.set(scmRef.canonical, scmRef);
|
|
1024
|
+
}
|
|
681
1025
|
for (const githubRef of refs.githubRefs) {
|
|
682
1026
|
releaseGithubRefMap.set(githubRef.canonical, githubRef);
|
|
683
1027
|
}
|
|
@@ -689,6 +1033,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
689
1033
|
for (const issueRef of refs.issueRefs) {
|
|
690
1034
|
releaseIssueRefs.add(issueRef);
|
|
691
1035
|
}
|
|
1036
|
+
for (const scmRef of refs.scmRefs) {
|
|
1037
|
+
releaseScmRefMap.set(scmRef.canonical, scmRef);
|
|
1038
|
+
}
|
|
692
1039
|
for (const githubRef of refs.githubRefs) {
|
|
693
1040
|
releaseGithubRefMap.set(githubRef.canonical, githubRef);
|
|
694
1041
|
}
|
|
@@ -702,18 +1049,75 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
702
1049
|
return {
|
|
703
1050
|
releaseRequest: releaseRequest,
|
|
704
1051
|
releaseIssueRefs: [...releaseIssueRefs],
|
|
1052
|
+
releaseScmRefs: [...releaseScmRefMap.values()],
|
|
705
1053
|
releaseGithubRefs: [...releaseGithubRefMap.values()],
|
|
706
1054
|
requestFile: requestPath
|
|
707
1055
|
};
|
|
708
1056
|
}
|
|
1057
|
+
if (workflow.name === "deployment-gate-review") {
|
|
1058
|
+
const requestPath = ".agentops/requests/deployment.yaml";
|
|
1059
|
+
ensureReadablePath(policyEngine, requestPath, "deployment request");
|
|
1060
|
+
const deploymentRequest = validateDeploymentRequestCompleteness(readYamlFile(join(root, requestPath), deploymentRequestSchema, "deployment request"));
|
|
1061
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
1062
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
1063
|
+
const deploymentRefs = {
|
|
1064
|
+
issueRefs: new Set(deploymentRequest.issueRefs),
|
|
1065
|
+
scmRefMap: new Map(),
|
|
1066
|
+
githubRefMap: new Map()
|
|
1067
|
+
};
|
|
1068
|
+
for (const scmRef of normalizeScmReferences(deploymentRequest.issueRefs, scmRepoContext)) {
|
|
1069
|
+
deploymentRefs.scmRefMap.set(scmRef.canonical, scmRef);
|
|
1070
|
+
}
|
|
1071
|
+
for (const githubRef of normalizeGitHubReferences(deploymentRequest.issueRefs, gitHubRepoContext)) {
|
|
1072
|
+
deploymentRefs.githubRefMap.set(githubRef.canonical, githubRef);
|
|
1073
|
+
}
|
|
1074
|
+
for (const qaReportRef of deploymentRequest.qaReportRefs) {
|
|
1075
|
+
ensureReadablePath(policyEngine, qaReportRef, "QA report reference");
|
|
1076
|
+
ensureBundleContainsArtifactKind(root, qaReportRef, "qa-report", "QA report reference");
|
|
1077
|
+
addSourceReferences(deploymentRefs, loadLifecycleArtifactSourceReferences(root, qaReportRef));
|
|
1078
|
+
}
|
|
1079
|
+
for (const securityReportRef of deploymentRequest.securityReportRefs) {
|
|
1080
|
+
ensureReadablePath(policyEngine, securityReportRef, "security report reference");
|
|
1081
|
+
ensureBundleContainsArtifactKind(root, securityReportRef, "security-report", "security report reference");
|
|
1082
|
+
addSourceReferences(deploymentRefs, loadLifecycleArtifactSourceReferences(root, securityReportRef));
|
|
1083
|
+
}
|
|
1084
|
+
for (const releaseReportRef of deploymentRequest.releaseReportRefs) {
|
|
1085
|
+
ensureReadablePath(policyEngine, releaseReportRef, "release report reference");
|
|
1086
|
+
ensureBundleContainsArtifactKind(root, releaseReportRef, "release-report", "release report reference");
|
|
1087
|
+
addSourceReferences(deploymentRefs, loadLifecycleArtifactSourceReferences(root, releaseReportRef));
|
|
1088
|
+
}
|
|
1089
|
+
for (const pipelineReportRef of deploymentRequest.pipelineReportRefs) {
|
|
1090
|
+
ensureReadablePath(policyEngine, pipelineReportRef, "pipeline report reference");
|
|
1091
|
+
ensureBundleContainsArtifactKind(root, pipelineReportRef, "pipeline-report", "pipeline report reference");
|
|
1092
|
+
addSourceReferences(deploymentRefs, loadLifecycleArtifactSourceReferences(root, pipelineReportRef));
|
|
1093
|
+
}
|
|
1094
|
+
for (const evidenceSource of deploymentRequest.evidenceSources) {
|
|
1095
|
+
ensureReadablePath(policyEngine, evidenceSource, "deployment evidence source");
|
|
1096
|
+
if (!existsSync(join(root, evidenceSource))) {
|
|
1097
|
+
throw new Error(`Deployment evidence source not found: ${evidenceSource}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
deploymentRequest: deploymentRequest,
|
|
1102
|
+
deploymentIssueRefs: [...deploymentRefs.issueRefs],
|
|
1103
|
+
deploymentScmRefs: [...deploymentRefs.scmRefMap.values()],
|
|
1104
|
+
deploymentGithubRefs: [...deploymentRefs.githubRefMap.values()],
|
|
1105
|
+
requestFile: requestPath
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
709
1108
|
if (workflow.name === "incident-handoff") {
|
|
710
1109
|
const requestPath = ".agentops/requests/incident.yaml";
|
|
711
1110
|
ensureReadablePath(policyEngine, requestPath, "incident request");
|
|
712
1111
|
const incidentRequest = validateIncidentRequestCompleteness(readYamlFile(join(root, requestPath), incidentRequestSchema, "incident request"));
|
|
713
|
-
const
|
|
1112
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
1113
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
714
1114
|
const incidentIssueRefs = new Set(incidentRequest.issueRefs);
|
|
1115
|
+
const incidentScmRefMap = new Map();
|
|
715
1116
|
const incidentGithubRefMap = new Map();
|
|
716
|
-
for (const
|
|
1117
|
+
for (const scmRef of normalizeScmReferences(incidentRequest.issueRefs, scmRepoContext)) {
|
|
1118
|
+
incidentScmRefMap.set(scmRef.canonical, scmRef);
|
|
1119
|
+
}
|
|
1120
|
+
for (const githubRef of normalizeGitHubReferences(incidentRequest.issueRefs, gitHubRepoContext)) {
|
|
717
1121
|
incidentGithubRefMap.set(githubRef.canonical, githubRef);
|
|
718
1122
|
}
|
|
719
1123
|
for (const releaseReportRef of incidentRequest.releaseReportRefs) {
|
|
@@ -723,6 +1127,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
723
1127
|
for (const issueRef of refs.issueRefs) {
|
|
724
1128
|
incidentIssueRefs.add(issueRef);
|
|
725
1129
|
}
|
|
1130
|
+
for (const scmRef of refs.scmRefs) {
|
|
1131
|
+
incidentScmRefMap.set(scmRef.canonical, scmRef);
|
|
1132
|
+
}
|
|
726
1133
|
for (const githubRef of refs.githubRefs) {
|
|
727
1134
|
incidentGithubRefMap.set(githubRef.canonical, githubRef);
|
|
728
1135
|
}
|
|
@@ -736,6 +1143,7 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
736
1143
|
return {
|
|
737
1144
|
incidentRequest: incidentRequest,
|
|
738
1145
|
incidentIssueRefs: [...incidentIssueRefs],
|
|
1146
|
+
incidentScmRefs: [...incidentScmRefMap.values()],
|
|
739
1147
|
incidentGithubRefs: [...incidentGithubRefMap.values()],
|
|
740
1148
|
requestFile: requestPath
|
|
741
1149
|
};
|
|
@@ -744,10 +1152,15 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
744
1152
|
const requestPath = ".agentops/requests/maintenance.yaml";
|
|
745
1153
|
ensureReadablePath(policyEngine, requestPath, "maintenance request");
|
|
746
1154
|
const maintenanceRequest = validateMaintenanceRequestCompleteness(readYamlFile(join(root, requestPath), maintenanceRequestSchema, "maintenance request"));
|
|
747
|
-
const
|
|
1155
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
1156
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
748
1157
|
const maintenanceIssueRefs = new Set(maintenanceRequest.issueRefs);
|
|
1158
|
+
const maintenanceScmRefMap = new Map();
|
|
749
1159
|
const maintenanceGithubRefMap = new Map();
|
|
750
|
-
for (const
|
|
1160
|
+
for (const scmRef of normalizeScmReferences(maintenanceRequest.issueRefs, scmRepoContext)) {
|
|
1161
|
+
maintenanceScmRefMap.set(scmRef.canonical, scmRef);
|
|
1162
|
+
}
|
|
1163
|
+
for (const githubRef of normalizeGitHubReferences(maintenanceRequest.issueRefs, gitHubRepoContext)) {
|
|
751
1164
|
maintenanceGithubRefMap.set(githubRef.canonical, githubRef);
|
|
752
1165
|
}
|
|
753
1166
|
for (const releaseReportRef of maintenanceRequest.releaseReportRefs) {
|
|
@@ -757,6 +1170,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
757
1170
|
for (const issueRef of refs.issueRefs) {
|
|
758
1171
|
maintenanceIssueRefs.add(issueRef);
|
|
759
1172
|
}
|
|
1173
|
+
for (const scmRef of refs.scmRefs) {
|
|
1174
|
+
maintenanceScmRefMap.set(scmRef.canonical, scmRef);
|
|
1175
|
+
}
|
|
760
1176
|
for (const githubRef of refs.githubRefs) {
|
|
761
1177
|
maintenanceGithubRefMap.set(githubRef.canonical, githubRef);
|
|
762
1178
|
}
|
|
@@ -776,6 +1192,7 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
776
1192
|
return {
|
|
777
1193
|
maintenanceRequest: maintenanceRequest,
|
|
778
1194
|
maintenanceIssueRefs: [...maintenanceIssueRefs],
|
|
1195
|
+
maintenanceScmRefs: [...maintenanceScmRefMap.values()],
|
|
779
1196
|
maintenanceGithubRefs: [...maintenanceGithubRefMap.values()],
|
|
780
1197
|
requestFile: requestPath
|
|
781
1198
|
};
|
|
@@ -1421,6 +1838,14 @@ function ensureInitFiles(root) {
|
|
|
1421
1838
|
path: join(workflowsDir, "release-readiness.yaml"),
|
|
1422
1839
|
contents: releaseWorkflowTemplate
|
|
1423
1840
|
},
|
|
1841
|
+
{
|
|
1842
|
+
path: join(workflowsDir, "pipeline-evidence-review.yaml"),
|
|
1843
|
+
contents: pipelineWorkflowTemplate
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
path: join(workflowsDir, "deployment-gate-review.yaml"),
|
|
1847
|
+
contents: deploymentGateWorkflowTemplate
|
|
1848
|
+
},
|
|
1424
1849
|
{
|
|
1425
1850
|
path: join(workflowsDir, "incident-handoff.yaml"),
|
|
1426
1851
|
contents: incidentWorkflowTemplate
|