@h9-foundry/agentforge-cli 0.8.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 +17 -5
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +482 -21
- 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,12 +6,13 @@ 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";
|
|
13
13
|
export { checkReleaseReadiness, getReleaseGuide, renderReleaseGuide, TARGET_NPM_SCOPE, EXPECTED_PUBLIC_PACKAGES } from "./internal/release-preflight.js";
|
|
14
14
|
export { verifyReleaseArtifacts } from "./internal/release-verification.js";
|
|
15
|
+
export const startupPresetNames = ["planning-discovery"];
|
|
15
16
|
const agentforgeConfigTemplate = `version: 1
|
|
16
17
|
project:
|
|
17
18
|
name: REPO_NAME
|
|
@@ -238,7 +239,7 @@ description: Validate a bounded release-readiness request while keeping trusted
|
|
|
238
239
|
trigger: manual
|
|
239
240
|
catalog:
|
|
240
241
|
domain: release
|
|
241
|
-
supportLevel:
|
|
242
|
+
supportLevel: official
|
|
242
243
|
maturity: mvp
|
|
243
244
|
trustScope: official-core-only
|
|
244
245
|
nodes:
|
|
@@ -258,6 +259,58 @@ nodes:
|
|
|
258
259
|
kind: report
|
|
259
260
|
outputs_to: reports.final
|
|
260
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
|
+
`;
|
|
261
314
|
const incidentWorkflowTemplate = `version: 1
|
|
262
315
|
name: incident-handoff
|
|
263
316
|
description: Validate staged incident evidence while keeping the default path local, read-only, and explicit.
|
|
@@ -327,24 +380,90 @@ function runGit(root, args) {
|
|
|
327
380
|
return "";
|
|
328
381
|
}
|
|
329
382
|
}
|
|
330
|
-
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) {
|
|
331
394
|
const trimmed = value.trim();
|
|
332
|
-
const sshMatch = trimmed.match(/^git@([^:]+):(
|
|
395
|
+
const sshMatch = trimmed.match(/^git@([^:]+):(.+?)(?:\.git)?$/i);
|
|
333
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
|
+
}
|
|
334
413
|
return {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
414
|
+
platform,
|
|
415
|
+
host,
|
|
416
|
+
namespace,
|
|
417
|
+
repo
|
|
338
418
|
};
|
|
339
419
|
}
|
|
340
|
-
const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/(
|
|
420
|
+
const httpsMatch = trimmed.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?(?:\/)?$/i);
|
|
341
421
|
if (!httpsMatch) {
|
|
342
422
|
return undefined;
|
|
343
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
|
+
}
|
|
452
|
+
return {
|
|
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
|
+
}
|
|
344
463
|
return {
|
|
345
|
-
host:
|
|
346
|
-
|
|
347
|
-
repo:
|
|
464
|
+
host: parsed.host,
|
|
465
|
+
namespace: parsed.namespace,
|
|
466
|
+
repo: parsed.repo
|
|
348
467
|
};
|
|
349
468
|
}
|
|
350
469
|
function inferGitHubRepoContext(root) {
|
|
@@ -370,6 +489,60 @@ function inferGitHubRepoContext(root) {
|
|
|
370
489
|
const remoteUrl = runGit(root, ["config", "--get", "remote.origin.url"]);
|
|
371
490
|
return remoteUrl ? parseGitHubRepositoryUrl(remoteUrl) : undefined;
|
|
372
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
|
+
}
|
|
373
546
|
function normalizeGitHubReference(rawValue, repoContext) {
|
|
374
547
|
const raw = rawValue.trim();
|
|
375
548
|
if (!raw) {
|
|
@@ -425,6 +598,86 @@ function normalizeGitHubReferences(rawValues, repoContext) {
|
|
|
425
598
|
}
|
|
426
599
|
return normalized;
|
|
427
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
|
+
}
|
|
428
681
|
export function mapWorkflowRunStatusToGitHubStatus(workflow, localRunStatus) {
|
|
429
682
|
if (localRunStatus === "success") {
|
|
430
683
|
return {
|
|
@@ -543,6 +796,35 @@ function validateReleaseRequestCompleteness(request) {
|
|
|
543
796
|
}
|
|
544
797
|
return request;
|
|
545
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
|
+
}
|
|
546
828
|
function loadLifecycleArtifactKinds(root, bundleRef) {
|
|
547
829
|
const bundlePath = join(root, bundleRef);
|
|
548
830
|
if (!existsSync(bundlePath)) {
|
|
@@ -557,22 +839,31 @@ function loadLifecycleArtifactSourceReferences(root, bundleRef) {
|
|
|
557
839
|
throw new Error(`Referenced bundle not found: ${bundleRef}`);
|
|
558
840
|
}
|
|
559
841
|
const bundle = auditBundleSchema.parse(JSON.parse(readFileSync(bundlePath, "utf8")));
|
|
560
|
-
const
|
|
842
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
843
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
561
844
|
const issueRefs = new Set();
|
|
845
|
+
const scmRefs = new Map();
|
|
562
846
|
const githubRefs = new Map();
|
|
563
847
|
for (const artifact of bundle.lifecycleArtifacts) {
|
|
564
848
|
for (const issueRef of artifact.source.issueRefs) {
|
|
565
849
|
issueRefs.add(issueRef);
|
|
566
850
|
}
|
|
851
|
+
for (const scmRef of artifact.source.scmRefs ?? []) {
|
|
852
|
+
scmRefs.set(scmRef.canonical, scmRef);
|
|
853
|
+
}
|
|
567
854
|
for (const githubRef of artifact.source.githubRefs ?? []) {
|
|
568
855
|
githubRefs.set(githubRef.canonical, githubRef);
|
|
569
856
|
}
|
|
570
|
-
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)) {
|
|
571
861
|
githubRefs.set(githubRef.canonical, githubRef);
|
|
572
862
|
}
|
|
573
863
|
}
|
|
574
864
|
return {
|
|
575
865
|
issueRefs: [...issueRefs],
|
|
866
|
+
scmRefs: [...scmRefs.values()],
|
|
576
867
|
githubRefs: [...githubRefs.values()]
|
|
577
868
|
};
|
|
578
869
|
}
|
|
@@ -583,9 +874,11 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
583
874
|
const requestPath = ".agentops/requests/planning.yaml";
|
|
584
875
|
ensureReadablePath(policyEngine, requestPath, "planning request");
|
|
585
876
|
const planningRequest = validatePlanningRequestCompleteness(readYamlFile(join(root, requestPath), planningRequestSchema, "planning request"));
|
|
877
|
+
const planningScmRefs = normalizeScmReferences(planningRequest.issueRefs, inferScmRepoContext(root));
|
|
586
878
|
const planningGithubRefs = normalizeGitHubReferences(planningRequest.issueRefs, inferGitHubRepoContext(root));
|
|
587
879
|
return {
|
|
588
880
|
planningRequest,
|
|
881
|
+
planningScmRefs,
|
|
589
882
|
planningGithubRefs,
|
|
590
883
|
requestFile: requestPath
|
|
591
884
|
};
|
|
@@ -627,10 +920,11 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
627
920
|
}
|
|
628
921
|
const referencedSourceRefs = qaRequest.targetRef.endsWith("bundle.json")
|
|
629
922
|
? loadLifecycleArtifactSourceReferences(root, qaRequest.targetRef)
|
|
630
|
-
: { issueRefs: [], githubRefs: [] };
|
|
923
|
+
: { issueRefs: [], scmRefs: [], githubRefs: [] };
|
|
631
924
|
return {
|
|
632
925
|
qaRequest: qaRequest,
|
|
633
926
|
qaIssueRefs: referencedSourceRefs.issueRefs,
|
|
927
|
+
qaScmRefs: referencedSourceRefs.scmRefs,
|
|
634
928
|
qaGithubRefs: referencedSourceRefs.githubRefs,
|
|
635
929
|
requestFile: requestPath
|
|
636
930
|
};
|
|
@@ -655,20 +949,68 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
655
949
|
}
|
|
656
950
|
const referencedSourceRefs = securityRequest.targetRef.endsWith("bundle.json")
|
|
657
951
|
? loadLifecycleArtifactSourceReferences(root, securityRequest.targetRef)
|
|
658
|
-
: { issueRefs: [], githubRefs: [] };
|
|
952
|
+
: { issueRefs: [], scmRefs: [], githubRefs: [] };
|
|
659
953
|
return {
|
|
660
954
|
securityRequest: securityRequest,
|
|
661
955
|
securityTargetArtifactKinds: referencedArtifactKinds,
|
|
662
956
|
securityIssueRefs: referencedSourceRefs.issueRefs,
|
|
957
|
+
securityScmRefs: referencedSourceRefs.scmRefs,
|
|
663
958
|
securityGithubRefs: referencedSourceRefs.githubRefs,
|
|
664
959
|
requestFile: requestPath
|
|
665
960
|
};
|
|
666
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
|
+
}
|
|
667
1008
|
if (workflow.name === "release-readiness") {
|
|
668
1009
|
const requestPath = ".agentops/requests/release.yaml";
|
|
669
1010
|
ensureReadablePath(policyEngine, requestPath, "release request");
|
|
670
1011
|
const releaseRequest = validateReleaseRequestCompleteness(readYamlFile(join(root, requestPath), releaseRequestSchema, "release request"));
|
|
671
1012
|
const releaseIssueRefs = new Set();
|
|
1013
|
+
const releaseScmRefMap = new Map();
|
|
672
1014
|
const releaseGithubRefMap = new Map();
|
|
673
1015
|
for (const qaReportRef of releaseRequest.qaReportRefs) {
|
|
674
1016
|
ensureReadablePath(policyEngine, qaReportRef, "QA report reference");
|
|
@@ -677,6 +1019,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
677
1019
|
for (const issueRef of refs.issueRefs) {
|
|
678
1020
|
releaseIssueRefs.add(issueRef);
|
|
679
1021
|
}
|
|
1022
|
+
for (const scmRef of refs.scmRefs) {
|
|
1023
|
+
releaseScmRefMap.set(scmRef.canonical, scmRef);
|
|
1024
|
+
}
|
|
680
1025
|
for (const githubRef of refs.githubRefs) {
|
|
681
1026
|
releaseGithubRefMap.set(githubRef.canonical, githubRef);
|
|
682
1027
|
}
|
|
@@ -688,6 +1033,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
688
1033
|
for (const issueRef of refs.issueRefs) {
|
|
689
1034
|
releaseIssueRefs.add(issueRef);
|
|
690
1035
|
}
|
|
1036
|
+
for (const scmRef of refs.scmRefs) {
|
|
1037
|
+
releaseScmRefMap.set(scmRef.canonical, scmRef);
|
|
1038
|
+
}
|
|
691
1039
|
for (const githubRef of refs.githubRefs) {
|
|
692
1040
|
releaseGithubRefMap.set(githubRef.canonical, githubRef);
|
|
693
1041
|
}
|
|
@@ -701,18 +1049,75 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
701
1049
|
return {
|
|
702
1050
|
releaseRequest: releaseRequest,
|
|
703
1051
|
releaseIssueRefs: [...releaseIssueRefs],
|
|
1052
|
+
releaseScmRefs: [...releaseScmRefMap.values()],
|
|
704
1053
|
releaseGithubRefs: [...releaseGithubRefMap.values()],
|
|
705
1054
|
requestFile: requestPath
|
|
706
1055
|
};
|
|
707
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
|
+
}
|
|
708
1108
|
if (workflow.name === "incident-handoff") {
|
|
709
1109
|
const requestPath = ".agentops/requests/incident.yaml";
|
|
710
1110
|
ensureReadablePath(policyEngine, requestPath, "incident request");
|
|
711
1111
|
const incidentRequest = validateIncidentRequestCompleteness(readYamlFile(join(root, requestPath), incidentRequestSchema, "incident request"));
|
|
712
|
-
const
|
|
1112
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
1113
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
713
1114
|
const incidentIssueRefs = new Set(incidentRequest.issueRefs);
|
|
1115
|
+
const incidentScmRefMap = new Map();
|
|
714
1116
|
const incidentGithubRefMap = new Map();
|
|
715
|
-
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)) {
|
|
716
1121
|
incidentGithubRefMap.set(githubRef.canonical, githubRef);
|
|
717
1122
|
}
|
|
718
1123
|
for (const releaseReportRef of incidentRequest.releaseReportRefs) {
|
|
@@ -722,6 +1127,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
722
1127
|
for (const issueRef of refs.issueRefs) {
|
|
723
1128
|
incidentIssueRefs.add(issueRef);
|
|
724
1129
|
}
|
|
1130
|
+
for (const scmRef of refs.scmRefs) {
|
|
1131
|
+
incidentScmRefMap.set(scmRef.canonical, scmRef);
|
|
1132
|
+
}
|
|
725
1133
|
for (const githubRef of refs.githubRefs) {
|
|
726
1134
|
incidentGithubRefMap.set(githubRef.canonical, githubRef);
|
|
727
1135
|
}
|
|
@@ -735,6 +1143,7 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
735
1143
|
return {
|
|
736
1144
|
incidentRequest: incidentRequest,
|
|
737
1145
|
incidentIssueRefs: [...incidentIssueRefs],
|
|
1146
|
+
incidentScmRefs: [...incidentScmRefMap.values()],
|
|
738
1147
|
incidentGithubRefs: [...incidentGithubRefMap.values()],
|
|
739
1148
|
requestFile: requestPath
|
|
740
1149
|
};
|
|
@@ -743,10 +1152,15 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
743
1152
|
const requestPath = ".agentops/requests/maintenance.yaml";
|
|
744
1153
|
ensureReadablePath(policyEngine, requestPath, "maintenance request");
|
|
745
1154
|
const maintenanceRequest = validateMaintenanceRequestCompleteness(readYamlFile(join(root, requestPath), maintenanceRequestSchema, "maintenance request"));
|
|
746
|
-
const
|
|
1155
|
+
const scmRepoContext = inferScmRepoContext(root);
|
|
1156
|
+
const gitHubRepoContext = inferGitHubRepoContext(root);
|
|
747
1157
|
const maintenanceIssueRefs = new Set(maintenanceRequest.issueRefs);
|
|
1158
|
+
const maintenanceScmRefMap = new Map();
|
|
748
1159
|
const maintenanceGithubRefMap = new Map();
|
|
749
|
-
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)) {
|
|
750
1164
|
maintenanceGithubRefMap.set(githubRef.canonical, githubRef);
|
|
751
1165
|
}
|
|
752
1166
|
for (const releaseReportRef of maintenanceRequest.releaseReportRefs) {
|
|
@@ -756,6 +1170,9 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
756
1170
|
for (const issueRef of refs.issueRefs) {
|
|
757
1171
|
maintenanceIssueRefs.add(issueRef);
|
|
758
1172
|
}
|
|
1173
|
+
for (const scmRef of refs.scmRefs) {
|
|
1174
|
+
maintenanceScmRefMap.set(scmRef.canonical, scmRef);
|
|
1175
|
+
}
|
|
759
1176
|
for (const githubRef of refs.githubRefs) {
|
|
760
1177
|
maintenanceGithubRefMap.set(githubRef.canonical, githubRef);
|
|
761
1178
|
}
|
|
@@ -775,6 +1192,7 @@ function prepareWorkflowInputs(workflow, root, policyEngine) {
|
|
|
775
1192
|
return {
|
|
776
1193
|
maintenanceRequest: maintenanceRequest,
|
|
777
1194
|
maintenanceIssueRefs: [...maintenanceIssueRefs],
|
|
1195
|
+
maintenanceScmRefs: [...maintenanceScmRefMap.values()],
|
|
778
1196
|
maintenanceGithubRefs: [...maintenanceGithubRefMap.values()],
|
|
779
1197
|
requestFile: requestPath
|
|
780
1198
|
};
|
|
@@ -1420,6 +1838,14 @@ function ensureInitFiles(root) {
|
|
|
1420
1838
|
path: join(workflowsDir, "release-readiness.yaml"),
|
|
1421
1839
|
contents: releaseWorkflowTemplate
|
|
1422
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
|
+
},
|
|
1423
1849
|
{
|
|
1424
1850
|
path: join(workflowsDir, "incident-handoff.yaml"),
|
|
1425
1851
|
contents: incidentWorkflowTemplate
|
|
@@ -1500,10 +1926,45 @@ function validateWorkflowAgents(workflow, agents, blockedPlugins) {
|
|
|
1500
1926
|
throw new Error(`Workflow agent is not registered: ${node.agent}`);
|
|
1501
1927
|
}
|
|
1502
1928
|
}
|
|
1503
|
-
|
|
1929
|
+
function createPlanningDiscoveryPresetRequest(root) {
|
|
1930
|
+
const repoName = root.split("/").at(-1) ?? "this repository";
|
|
1931
|
+
const pathHints = ["README.md", "package.json", "src", "docs"].filter((pathHint) => existsSync(join(root, pathHint)));
|
|
1932
|
+
return planningRequestSchema.parse({
|
|
1933
|
+
problemStatement: `Plan the next safe local-first improvement for ${repoName}.`,
|
|
1934
|
+
goals: ["Produce one planning brief artifact", "Identify a bounded next step before opening a pull request"],
|
|
1935
|
+
constraints: ["Keep the default path local-first and read-only", "Prefer a small, reviewable next change"],
|
|
1936
|
+
pathHints,
|
|
1937
|
+
assumptions: ["This preset is a starter request that can be edited after initialization if the repository needs different focus."]
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
function applyStartupPreset(root, preset) {
|
|
1941
|
+
const requestsRoot = join(root, ".agentops", "requests");
|
|
1942
|
+
ensureDirectory(requestsRoot);
|
|
1943
|
+
switch (preset) {
|
|
1944
|
+
case "planning-discovery": {
|
|
1945
|
+
const requestPath = join(requestsRoot, "planning.yaml");
|
|
1946
|
+
const created = !existsSync(requestPath);
|
|
1947
|
+
if (created) {
|
|
1948
|
+
writeYamlFile(requestPath, createPlanningDiscoveryPresetRequest(root));
|
|
1949
|
+
}
|
|
1950
|
+
return {
|
|
1951
|
+
preset,
|
|
1952
|
+
workflow: "planning-discovery",
|
|
1953
|
+
requestPath,
|
|
1954
|
+
created
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
export function initProject(cwd = process.cwd(), options) {
|
|
1504
1960
|
const root = findWorkspaceRoot(cwd);
|
|
1505
1961
|
const created = ensureInitFiles(root);
|
|
1506
|
-
|
|
1962
|
+
const preset = options?.preset ? applyStartupPreset(root, options.preset) : undefined;
|
|
1963
|
+
return {
|
|
1964
|
+
root,
|
|
1965
|
+
created,
|
|
1966
|
+
...(preset ? { preset } : {})
|
|
1967
|
+
};
|
|
1507
1968
|
}
|
|
1508
1969
|
export function scanProject(cwd = process.cwd()) {
|
|
1509
1970
|
const root = findWorkspaceRoot(cwd);
|