@h9-foundry/agentforge-cli 0.4.2 → 0.6.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.
@@ -1,4 +1,6 @@
1
- import { agentManifestSchema, agentOutputSchema } from "@h9-foundry/agentforge-schemas";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { agentManifestSchema, agentOutputSchema, designArtifactSchema, implementationArtifactSchema, implementationInventorySchema, qaRequestSchema, planningArtifactSchema } from "@h9-foundry/agentforge-schemas";
2
4
  const contextCollectorAgent = {
3
5
  manifest: agentManifestSchema.parse({
4
6
  version: 1,
@@ -22,6 +24,12 @@ const contextCollectorAgent = {
22
24
  sections: ["repo", "changes", "context"],
23
25
  minimalContext: true
24
26
  },
27
+ catalog: {
28
+ domain: "foundation",
29
+ supportLevel: "official",
30
+ maturity: "mvp",
31
+ trustScope: "official-core-only"
32
+ },
25
33
  trust: {
26
34
  tier: "core",
27
35
  source: "official",
@@ -49,6 +57,929 @@ const contextCollectorAgent = {
49
57
  });
50
58
  }
51
59
  };
60
+ function isRecord(value) {
61
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
62
+ }
63
+ function asStringArray(value) {
64
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
65
+ }
66
+ function parsePackageScripts(packageJsonPath) {
67
+ if (!existsSync(packageJsonPath)) {
68
+ return {};
69
+ }
70
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
71
+ if (!isRecord(parsed) || !isRecord(parsed.scripts)) {
72
+ return {};
73
+ }
74
+ return Object.fromEntries(Object.entries(parsed.scripts).filter((entry) => typeof entry[1] === "string"));
75
+ }
76
+ const allowedValidationScriptNames = new Set(["test", "lint", "typecheck", "build", "build:packages", "release:verify"]);
77
+ function normalizeRequestedCommand(command) {
78
+ return command.trim().replace(/\s+/g, " ");
79
+ }
80
+ function buildValidationCommand(packageManager, scriptName, packageName) {
81
+ if (packageName) {
82
+ return `${packageManager} --filter ${packageName} ${scriptName}`;
83
+ }
84
+ return `${packageManager} ${scriptName}`;
85
+ }
86
+ function derivePackageScope(pathValue) {
87
+ const segments = pathValue.split("/").filter(Boolean);
88
+ if (segments.length < 2) {
89
+ return undefined;
90
+ }
91
+ const [topLevel, scope] = segments;
92
+ if (topLevel === "packages" || topLevel === "agents" || topLevel === "adapters") {
93
+ return `${topLevel}/${scope}`;
94
+ }
95
+ return undefined;
96
+ }
97
+ function getWorkflowInput(stateSlice, key) {
98
+ if (!isRecord(stateSlice.workflowInputs)) {
99
+ return undefined;
100
+ }
101
+ return stateSlice.workflowInputs[key];
102
+ }
103
+ function buildArtifactEnvelopeBase(state, summary, inputRefs, issueRefs) {
104
+ return {
105
+ schemaVersion: state.version,
106
+ workflow: {
107
+ name: state.workflow
108
+ },
109
+ source: {
110
+ sourceType: "workflow-run",
111
+ runId: state.runId,
112
+ inputRefs: [...inputRefs],
113
+ issueRefs: [...issueRefs]
114
+ },
115
+ status: "complete",
116
+ generatedAt: new Date().toISOString(),
117
+ repo: {
118
+ root: state.repo.root,
119
+ name: state.repo.name,
120
+ branch: state.repo.branch
121
+ },
122
+ provenance: {
123
+ generatedBy: "agentforge-runtime",
124
+ schemaVersion: state.version,
125
+ executionEnvironment: state.context.ciExecution ? "ci" : "local",
126
+ repoRoot: state.repo.root
127
+ },
128
+ redaction: {
129
+ applied: true,
130
+ strategyVersion: "1.0.0",
131
+ categories: ["github-token", "api-key", "aws-key", "bearer-token", "password", "private-key"]
132
+ },
133
+ auditLink: {
134
+ entryIds: [],
135
+ findingIds: [],
136
+ proposedActionIds: []
137
+ },
138
+ summary
139
+ };
140
+ }
141
+ const planningIntakeAgent = {
142
+ manifest: agentManifestSchema.parse({
143
+ version: 1,
144
+ name: "planning-intake",
145
+ displayName: "Planning Intake",
146
+ category: "planning",
147
+ runtime: {
148
+ minVersion: "0.1.0",
149
+ kind: "deterministic"
150
+ },
151
+ permissions: {
152
+ model: false,
153
+ network: false,
154
+ tools: [],
155
+ readPaths: [".agentops/requests/**"],
156
+ writePaths: []
157
+ },
158
+ inputs: ["workflowInputs", "repo"],
159
+ outputs: ["summary", "metadata"],
160
+ contextPolicy: {
161
+ sections: ["workflowInputs", "repo", "context"],
162
+ minimalContext: true
163
+ },
164
+ catalog: {
165
+ domain: "plan",
166
+ supportLevel: "internal",
167
+ maturity: "mvp",
168
+ trustScope: "official-core-only"
169
+ },
170
+ trust: {
171
+ tier: "core",
172
+ source: "official",
173
+ reviewed: true
174
+ }
175
+ }),
176
+ outputSchema: agentOutputSchema,
177
+ async execute({ stateSlice }) {
178
+ const planningRequest = getWorkflowInput(stateSlice, "planningRequest");
179
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
180
+ if (!planningRequest) {
181
+ throw new Error("planning-discovery requires a validated planning request before runtime execution.");
182
+ }
183
+ return agentOutputSchema.parse({
184
+ summary: `Loaded planning request from ${requestFile ?? ".agentops/requests/planning.yaml"}.`,
185
+ findings: [],
186
+ proposedActions: [],
187
+ lifecycleArtifacts: [],
188
+ requestedTools: [],
189
+ blockedActionFlags: [],
190
+ metadata: {
191
+ requestFile,
192
+ problemStatement: planningRequest.problemStatement,
193
+ goals: planningRequest.goals,
194
+ constraints: planningRequest.constraints,
195
+ issueRefs: planningRequest.issueRefs,
196
+ pathHints: planningRequest.pathHints
197
+ }
198
+ });
199
+ }
200
+ };
201
+ const planningAnalystAgent = {
202
+ manifest: agentManifestSchema.parse({
203
+ version: 1,
204
+ name: "planning-analyst",
205
+ displayName: "Planning Analyst",
206
+ category: "planning",
207
+ runtime: {
208
+ minVersion: "0.1.0",
209
+ kind: "reasoning"
210
+ },
211
+ permissions: {
212
+ model: true,
213
+ network: false,
214
+ tools: [],
215
+ readPaths: ["**/*"],
216
+ writePaths: []
217
+ },
218
+ inputs: ["workflowInputs", "repo", "changes", "agentResults"],
219
+ outputs: ["lifecycleArtifacts"],
220
+ contextPolicy: {
221
+ sections: ["workflowInputs", "repo", "changes", "agentResults"],
222
+ minimalContext: true
223
+ },
224
+ catalog: {
225
+ domain: "plan",
226
+ supportLevel: "official",
227
+ maturity: "mvp",
228
+ trustScope: "official-core-only"
229
+ },
230
+ trust: {
231
+ tier: "core",
232
+ source: "official",
233
+ reviewed: true
234
+ }
235
+ }),
236
+ outputSchema: agentOutputSchema,
237
+ async execute({ state, stateSlice }) {
238
+ const planningRequest = getWorkflowInput(stateSlice, "planningRequest");
239
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
240
+ if (!planningRequest) {
241
+ throw new Error("planning-discovery requires a validated planning request before planning analysis.");
242
+ }
243
+ const topLevelHints = [...new Set(planningRequest.pathHints.map((hint) => hint.split("/")[0] ?? hint).filter(Boolean))];
244
+ const objectives = planningRequest.goals.length > 0
245
+ ? planningRequest.goals
246
+ : [`Produce a bounded plan for: ${planningRequest.problemStatement}`];
247
+ const inScope = planningRequest.pathHints.length > 0
248
+ ? planningRequest.pathHints
249
+ : ["Repository discovery", "Planning brief synthesis", "Next-step recommendation"];
250
+ const outOfScope = ["Source-file mutation", "Network-backed intake", "Architecture/design decisions"];
251
+ const openQuestions = [
252
+ ...(planningRequest.issueRefs.length === 0 ? ["Should this planning work be linked to a tracked issue?"] : []),
253
+ ...(planningRequest.pathHints.length === 0 ? ["Which repository paths should be prioritized in follow-up design work?"] : [])
254
+ ];
255
+ const candidateWorkstreams = topLevelHints.length > 0 ? topLevelHints : state.changes.impactedPaths;
256
+ const risks = [
257
+ ...(planningRequest.pathHints.length === 0 ? ["Impact area is broad because no path hints were supplied."] : []),
258
+ ...(planningRequest.assumptions.length === 0 ? ["Planning assumptions were not supplied and may need confirmation."] : [])
259
+ ];
260
+ const summary = `Planning brief scoped ${objectives.length} objective(s) for ${state.repo.name}.`;
261
+ const planningBrief = planningArtifactSchema.parse({
262
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/planning.yaml"], planningRequest.issueRefs),
263
+ artifactKind: "planning-brief",
264
+ lifecycleDomain: "plan",
265
+ workflow: {
266
+ name: state.workflow,
267
+ displayName: "Planning And Discovery"
268
+ },
269
+ payload: {
270
+ problemStatement: planningRequest.problemStatement,
271
+ objectives,
272
+ constraints: planningRequest.constraints,
273
+ assumptions: planningRequest.assumptions,
274
+ inScope,
275
+ outOfScope,
276
+ recommendedNextSteps: [
277
+ "Review the generated planning brief and refine missing constraints or path hints.",
278
+ "Open or update follow-on implementation issues for the accepted planning scope.",
279
+ "Use the planning brief as input to `architecture-design-review` for the next design slice."
280
+ ],
281
+ stakeholders: planningRequest.issueRefs.length > 0 ? ["Maintainers linked to the referenced issues"] : [],
282
+ risks,
283
+ openQuestions,
284
+ candidateWorkstreams,
285
+ linkedIssues: planningRequest.issueRefs
286
+ }
287
+ });
288
+ return agentOutputSchema.parse({
289
+ summary,
290
+ findings: [],
291
+ proposedActions: [],
292
+ lifecycleArtifacts: [planningBrief],
293
+ requestedTools: [],
294
+ blockedActionFlags: [],
295
+ confidence: 0.8,
296
+ metadata: {
297
+ deterministicContext: {
298
+ pathHints: planningRequest.pathHints,
299
+ impactedPaths: state.changes.impactedPaths,
300
+ repository: state.repo.name
301
+ },
302
+ synthesizedPlanning: {
303
+ recommendedNextSteps: planningBrief.payload.recommendedNextSteps,
304
+ openQuestions
305
+ }
306
+ }
307
+ });
308
+ }
309
+ };
310
+ const designIntakeAgent = {
311
+ manifest: agentManifestSchema.parse({
312
+ version: 1,
313
+ name: "design-intake",
314
+ displayName: "Design Intake",
315
+ category: "design",
316
+ runtime: {
317
+ minVersion: "0.1.0",
318
+ kind: "deterministic"
319
+ },
320
+ permissions: {
321
+ model: false,
322
+ network: false,
323
+ tools: [],
324
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
325
+ writePaths: []
326
+ },
327
+ inputs: ["workflowInputs", "repo"],
328
+ outputs: ["summary", "metadata"],
329
+ contextPolicy: {
330
+ sections: ["workflowInputs", "repo", "context"],
331
+ minimalContext: true
332
+ },
333
+ catalog: {
334
+ domain: "design",
335
+ supportLevel: "internal",
336
+ maturity: "mvp",
337
+ trustScope: "official-core-only"
338
+ },
339
+ trust: {
340
+ tier: "core",
341
+ source: "official",
342
+ reviewed: true
343
+ }
344
+ }),
345
+ outputSchema: agentOutputSchema,
346
+ async execute({ stateSlice }) {
347
+ const designRequest = getWorkflowInput(stateSlice, "designRequest");
348
+ const planningBrief = getWorkflowInput(stateSlice, "planningBrief");
349
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
350
+ if (!designRequest || !planningBrief) {
351
+ throw new Error("architecture-design-review requires a validated design request and planning brief before runtime execution.");
352
+ }
353
+ return agentOutputSchema.parse({
354
+ summary: `Loaded design request from ${requestFile ?? ".agentops/requests/design.yaml"} with planning brief ${designRequest.planningBriefRef}.`,
355
+ findings: [],
356
+ proposedActions: [],
357
+ lifecycleArtifacts: [],
358
+ requestedTools: [],
359
+ blockedActionFlags: [],
360
+ metadata: {
361
+ requestFile,
362
+ planningBriefRef: designRequest.planningBriefRef,
363
+ decisionTarget: designRequest.decisionTarget,
364
+ planningObjectives: planningBrief.payload.objectives
365
+ }
366
+ });
367
+ }
368
+ };
369
+ const designInventoryAgent = {
370
+ manifest: agentManifestSchema.parse({
371
+ version: 1,
372
+ name: "design-inventory",
373
+ displayName: "Design Inventory",
374
+ category: "design",
375
+ runtime: {
376
+ minVersion: "0.1.0",
377
+ kind: "deterministic"
378
+ },
379
+ permissions: {
380
+ model: false,
381
+ network: false,
382
+ tools: ["filesystem.list-files"],
383
+ readPaths: ["**/*"],
384
+ writePaths: []
385
+ },
386
+ inputs: ["workflowInputs", "repo", "changes"],
387
+ outputs: ["summary", "metadata"],
388
+ contextPolicy: {
389
+ sections: ["workflowInputs", "repo", "changes"],
390
+ minimalContext: true
391
+ },
392
+ catalog: {
393
+ domain: "design",
394
+ supportLevel: "internal",
395
+ maturity: "mvp",
396
+ trustScope: "official-core-only"
397
+ },
398
+ trust: {
399
+ tier: "core",
400
+ source: "official",
401
+ reviewed: true
402
+ }
403
+ }),
404
+ outputSchema: agentOutputSchema,
405
+ async execute({ stateSlice, invokeTool }) {
406
+ const designRequest = getWorkflowInput(stateSlice, "designRequest");
407
+ const planningBrief = getWorkflowInput(stateSlice, "planningBrief");
408
+ if (!designRequest || !planningBrief) {
409
+ throw new Error("architecture-design-review requires deterministic inventory inputs before design analysis.");
410
+ }
411
+ const candidatePaths = [...new Set([...designRequest.pathHints, ...(stateSlice.changes?.impactedPaths ?? [])])];
412
+ const repoRoot = stateSlice.repo?.root;
413
+ const inspectedPaths = candidatePaths
414
+ .filter((pathHint) => {
415
+ if (!pathHint) {
416
+ return false;
417
+ }
418
+ const finalSegment = pathHint.split("/").at(-1) ?? pathHint;
419
+ if (pathHint !== ".agentops" && finalSegment.includes(".")) {
420
+ return false;
421
+ }
422
+ if (!repoRoot) {
423
+ return true;
424
+ }
425
+ return existsSync(join(repoRoot, pathHint));
426
+ })
427
+ .slice(0, 8);
428
+ const listedEntries = [];
429
+ for (const pathHint of inspectedPaths) {
430
+ const listed = await invokeTool({
431
+ tool: "filesystem.list-files",
432
+ input: { path: pathHint },
433
+ requestedBy: "design-inventory",
434
+ requestedAt: new Date().toISOString()
435
+ });
436
+ if (listed.status === "success" && isRecord(listed.output) && Array.isArray(listed.output.entries)) {
437
+ for (const entry of listed.output.entries) {
438
+ if (typeof entry === "string") {
439
+ listedEntries.push(`${pathHint}/${entry}`.replaceAll("//", "/"));
440
+ }
441
+ }
442
+ }
443
+ }
444
+ const impactedInterfaces = [
445
+ ...new Set([...candidatePaths, ...listedEntries].filter((entry) => entry.endsWith("src/index.ts") || entry.endsWith("package.json") || entry.endsWith("agent.manifest.json")))
446
+ ];
447
+ const schemaSurfaces = [
448
+ ...new Set([...candidatePaths, ...listedEntries].filter((entry) => entry.includes("packages/schemas") || entry.includes("schema") || entry.endsWith(".yaml")))
449
+ ];
450
+ const policySurfaces = [
451
+ ...new Set([...candidatePaths, ...listedEntries].filter((entry) => entry.includes("policy") || entry.includes("packages/policy-engine") || entry.includes(".agentops/policy.yaml")))
452
+ ];
453
+ return agentOutputSchema.parse({
454
+ summary: `Collected deterministic design inventory across ${inspectedPaths.length} candidate path(s).`,
455
+ findings: [],
456
+ proposedActions: [],
457
+ lifecycleArtifacts: [],
458
+ requestedTools: [],
459
+ blockedActionFlags: [],
460
+ metadata: {
461
+ inspectedPaths,
462
+ impactedInterfaces,
463
+ schemaSurfaces,
464
+ policySurfaces,
465
+ planningScope: planningBrief.payload.inScope
466
+ }
467
+ });
468
+ }
469
+ };
470
+ const implementationIntakeAgent = {
471
+ manifest: agentManifestSchema.parse({
472
+ version: 1,
473
+ name: "implementation-intake",
474
+ displayName: "Implementation Intake",
475
+ category: "implementation",
476
+ runtime: {
477
+ minVersion: "0.1.0",
478
+ kind: "deterministic"
479
+ },
480
+ permissions: {
481
+ model: false,
482
+ network: false,
483
+ tools: [],
484
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
485
+ writePaths: []
486
+ },
487
+ inputs: ["workflowInputs", "repo"],
488
+ outputs: ["summary", "metadata"],
489
+ contextPolicy: {
490
+ sections: ["workflowInputs", "repo", "context"],
491
+ minimalContext: true
492
+ },
493
+ catalog: {
494
+ domain: "build",
495
+ supportLevel: "internal",
496
+ maturity: "mvp",
497
+ trustScope: "official-core-only"
498
+ },
499
+ trust: {
500
+ tier: "core",
501
+ source: "official",
502
+ reviewed: true
503
+ }
504
+ }),
505
+ outputSchema: agentOutputSchema,
506
+ async execute({ stateSlice }) {
507
+ const implementationRequest = getWorkflowInput(stateSlice, "implementationRequest");
508
+ const designRecord = getWorkflowInput(stateSlice, "designRecord");
509
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
510
+ if (!implementationRequest || !designRecord) {
511
+ throw new Error("implementation-proposal requires a validated implementation request and design record before runtime execution.");
512
+ }
513
+ return agentOutputSchema.parse({
514
+ summary: `Loaded implementation request from ${requestFile ?? ".agentops/requests/implementation.yaml"} with design record ${implementationRequest.designRecordRef}.`,
515
+ findings: [],
516
+ proposedActions: [],
517
+ lifecycleArtifacts: [],
518
+ requestedTools: [],
519
+ blockedActionFlags: [],
520
+ metadata: {
521
+ requestFile,
522
+ designRecordRef: implementationRequest.designRecordRef,
523
+ implementationGoal: implementationRequest.implementationGoal,
524
+ approvalMode: implementationRequest.approvalMode,
525
+ targetPaths: implementationRequest.targetPaths,
526
+ validationCommands: implementationRequest.validationCommands,
527
+ designDecisionSummary: designRecord.payload.decisionSummary
528
+ }
529
+ });
530
+ }
531
+ };
532
+ const qaIntakeAgent = {
533
+ manifest: agentManifestSchema.parse({
534
+ version: 1,
535
+ name: "qa-intake",
536
+ displayName: "QA Intake",
537
+ category: "qa",
538
+ runtime: {
539
+ minVersion: "0.1.0",
540
+ kind: "deterministic"
541
+ },
542
+ permissions: {
543
+ model: false,
544
+ network: false,
545
+ tools: [],
546
+ readPaths: [".agentops/requests/**", ".agentops/runs/**"],
547
+ writePaths: []
548
+ },
549
+ inputs: ["workflowInputs", "repo"],
550
+ outputs: ["summary", "metadata"],
551
+ contextPolicy: {
552
+ sections: ["workflowInputs", "repo", "context"],
553
+ minimalContext: true
554
+ },
555
+ catalog: {
556
+ domain: "test",
557
+ supportLevel: "internal",
558
+ maturity: "mvp",
559
+ trustScope: "official-core-only"
560
+ },
561
+ trust: {
562
+ tier: "core",
563
+ source: "official",
564
+ reviewed: true
565
+ }
566
+ }),
567
+ outputSchema: agentOutputSchema,
568
+ async execute({ stateSlice }) {
569
+ const qaRequest = getWorkflowInput(stateSlice, "qaRequest");
570
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
571
+ if (!qaRequest) {
572
+ throw new Error("qa-review requires a validated QA request before runtime execution.");
573
+ }
574
+ const targetType = qaRequest.targetRef.endsWith("bundle.json")
575
+ ? "artifact-bundle"
576
+ : qaRequest.targetRef.endsWith(".xml") || qaRequest.targetRef.endsWith(".json") || qaRequest.targetRef.endsWith(".log")
577
+ ? "validation-output"
578
+ : "local-reference";
579
+ return agentOutputSchema.parse({
580
+ summary: `Loaded QA request from ${requestFile ?? ".agentops/requests/qa.yaml"} targeting ${qaRequest.targetRef}.`,
581
+ findings: [],
582
+ proposedActions: [],
583
+ lifecycleArtifacts: [],
584
+ requestedTools: [],
585
+ blockedActionFlags: [],
586
+ metadata: {
587
+ ...qaRequestSchema.parse({
588
+ ...qaRequest,
589
+ evidenceSources: [...new Set([qaRequest.targetRef, ...qaRequest.evidenceSources])]
590
+ }),
591
+ targetType
592
+ }
593
+ });
594
+ }
595
+ };
596
+ const implementationInventoryAgent = {
597
+ manifest: agentManifestSchema.parse({
598
+ version: 1,
599
+ name: "implementation-inventory",
600
+ displayName: "Implementation Inventory",
601
+ category: "implementation",
602
+ runtime: {
603
+ minVersion: "0.1.0",
604
+ kind: "deterministic"
605
+ },
606
+ permissions: {
607
+ model: false,
608
+ network: false,
609
+ tools: [],
610
+ readPaths: ["**/*"],
611
+ writePaths: []
612
+ },
613
+ inputs: ["workflowInputs", "repo", "changes"],
614
+ outputs: ["summary", "metadata"],
615
+ contextPolicy: {
616
+ sections: ["workflowInputs", "repo", "changes"],
617
+ minimalContext: true
618
+ },
619
+ catalog: {
620
+ domain: "build",
621
+ supportLevel: "internal",
622
+ maturity: "mvp",
623
+ trustScope: "official-core-only"
624
+ },
625
+ trust: {
626
+ tier: "core",
627
+ source: "official",
628
+ reviewed: true
629
+ }
630
+ }),
631
+ outputSchema: agentOutputSchema,
632
+ async execute({ stateSlice }) {
633
+ const implementationRequest = getWorkflowInput(stateSlice, "implementationRequest");
634
+ const designRecord = getWorkflowInput(stateSlice, "designRecord");
635
+ if (!implementationRequest || !designRecord) {
636
+ throw new Error("implementation-proposal requires deterministic inventory inputs before proposal analysis.");
637
+ }
638
+ const repoRoot = stateSlice.repo?.root;
639
+ const packageManager = stateSlice.repo?.packageManager || "pnpm";
640
+ const candidatePaths = [
641
+ ...new Set([
642
+ ...implementationRequest.targetPaths,
643
+ ...designRecord.payload.interfacesImpacted,
644
+ ...designRecord.payload.schemaChangesNeeded,
645
+ ...designRecord.payload.policyChangesNeeded
646
+ ])
647
+ ];
648
+ const resolvedAffectedPaths = candidatePaths.filter((pathValue) => {
649
+ if (!pathValue) {
650
+ return false;
651
+ }
652
+ if (!repoRoot) {
653
+ return true;
654
+ }
655
+ return existsSync(join(repoRoot, pathValue));
656
+ });
657
+ const affectedPackages = [...new Set(resolvedAffectedPaths.map(derivePackageScope).filter((value) => Boolean(value)))];
658
+ const entrypoints = [
659
+ ...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.endsWith("src/index.ts") || pathValue.endsWith("package.json") || pathValue.endsWith("agent.manifest.json")))
660
+ ];
661
+ const schemaSurfaces = [...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.includes("schema")))];
662
+ const policySurfaces = [
663
+ ...new Set(resolvedAffectedPaths.filter((pathValue) => pathValue.includes("policy") || pathValue.includes(".agentops/policy.yaml")))
664
+ ];
665
+ const discoveredValidationCommands = [];
666
+ const registerScripts = (packageJsonPath, source, packageName) => {
667
+ const scripts = parsePackageScripts(packageJsonPath);
668
+ for (const scriptName of Object.keys(scripts)) {
669
+ const command = buildValidationCommand(packageManager, scriptName, packageName);
670
+ discoveredValidationCommands.push({
671
+ command,
672
+ source,
673
+ classification: allowedValidationScriptNames.has(scriptName) ? "approval_required" : "deny",
674
+ reason: allowedValidationScriptNames.has(scriptName)
675
+ ? "Discovered from a bounded repository script; execution would still require approval."
676
+ : "Command is not in the bounded allowlist for implementation validation."
677
+ });
678
+ }
679
+ };
680
+ if (repoRoot) {
681
+ registerScripts(join(repoRoot, "package.json"), "package-script");
682
+ for (const packageScope of affectedPackages) {
683
+ const packageJsonPath = join(repoRoot, packageScope, "package.json");
684
+ if (!existsSync(packageJsonPath)) {
685
+ continue;
686
+ }
687
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
688
+ const packageName = isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : packageScope;
689
+ registerScripts(packageJsonPath, "workspace-script", packageName);
690
+ }
691
+ }
692
+ const normalizedRequestedCommands = implementationRequest.validationCommands.map(normalizeRequestedCommand);
693
+ const allowlistedCommands = new Set(discoveredValidationCommands
694
+ .filter((entry) => entry.classification === "approval_required")
695
+ .map((entry) => entry.command));
696
+ for (const requestedCommand of normalizedRequestedCommands) {
697
+ if (!allowlistedCommands.has(requestedCommand)) {
698
+ throw new Error(`Implementation request contains non-allowlisted validation command: ${requestedCommand}`);
699
+ }
700
+ discoveredValidationCommands.push({
701
+ command: requestedCommand,
702
+ source: "request",
703
+ classification: "approval_required",
704
+ reason: "Requested command matches a discovered allowlisted validation script."
705
+ });
706
+ }
707
+ const inventory = implementationInventorySchema.parse({
708
+ requestedTargetPaths: implementationRequest.targetPaths,
709
+ resolvedAffectedPaths,
710
+ affectedPackages,
711
+ entrypoints,
712
+ schemaSurfaces,
713
+ policySurfaces,
714
+ discoveredValidationCommands
715
+ });
716
+ return agentOutputSchema.parse({
717
+ summary: `Collected deterministic implementation inventory across ${inventory.resolvedAffectedPaths.length} path(s) and ${inventory.discoveredValidationCommands.length} validation command(s).`,
718
+ findings: [],
719
+ proposedActions: [],
720
+ lifecycleArtifacts: [],
721
+ requestedTools: [],
722
+ blockedActionFlags: [],
723
+ metadata: inventory
724
+ });
725
+ }
726
+ };
727
+ const implementationPlannerAgent = {
728
+ manifest: agentManifestSchema.parse({
729
+ version: 1,
730
+ name: "implementation-planner",
731
+ displayName: "Implementation Planner",
732
+ category: "implementation",
733
+ runtime: {
734
+ minVersion: "0.1.0",
735
+ kind: "reasoning"
736
+ },
737
+ permissions: {
738
+ model: true,
739
+ network: false,
740
+ tools: [],
741
+ readPaths: ["**/*"],
742
+ writePaths: []
743
+ },
744
+ inputs: ["workflowInputs", "repo", "changes", "agentResults"],
745
+ outputs: ["lifecycleArtifacts"],
746
+ contextPolicy: {
747
+ sections: ["workflowInputs", "repo", "changes", "agentResults"],
748
+ minimalContext: true
749
+ },
750
+ catalog: {
751
+ domain: "build",
752
+ supportLevel: "official",
753
+ maturity: "mvp",
754
+ trustScope: "official-core-only"
755
+ },
756
+ trust: {
757
+ tier: "core",
758
+ source: "official",
759
+ reviewed: true
760
+ }
761
+ }),
762
+ outputSchema: agentOutputSchema,
763
+ async execute({ state, stateSlice }) {
764
+ const implementationRequest = getWorkflowInput(stateSlice, "implementationRequest");
765
+ const designRecord = getWorkflowInput(stateSlice, "designRecord");
766
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
767
+ if (!implementationRequest || !designRecord) {
768
+ throw new Error("implementation-proposal requires validated implementation inputs before proposal analysis.");
769
+ }
770
+ const inventoryMetadata = implementationInventorySchema.safeParse(stateSlice.agentResults?.inventory?.metadata);
771
+ const inventory = inventoryMetadata.success ? inventoryMetadata.data : undefined;
772
+ const normalizedAffectedPaths = inventory && inventory.resolvedAffectedPaths.length > 0
773
+ ? inventory.resolvedAffectedPaths
774
+ : [
775
+ ...new Set([
776
+ ...implementationRequest.targetPaths,
777
+ ...designRecord.payload.interfacesImpacted,
778
+ ...designRecord.payload.schemaChangesNeeded,
779
+ ...designRecord.payload.policyChangesNeeded
780
+ ])
781
+ ];
782
+ const finalAffectedPaths = normalizedAffectedPaths.length > 0
783
+ ? normalizedAffectedPaths
784
+ : ["Repository paths still need deterministic build-surface confirmation."];
785
+ const proposedChanges = [
786
+ `Prepare a bounded implementation plan for ${implementationRequest.implementationGoal}.`,
787
+ ...finalAffectedPaths.slice(0, 5).map((pathValue) => `Plan targeted edits for ${pathValue}.`)
788
+ ];
789
+ const selectedCommands = inventory
790
+ ? inventory.discoveredValidationCommands.filter((entry) => entry.classification === "approval_required" &&
791
+ (implementationRequest.validationCommands.length === 0 || entry.source === "request"))
792
+ : [];
793
+ const validationPlan = selectedCommands.length > 0
794
+ ? selectedCommands.map((entry) => `Command \`${entry.command}\` is available but approval-required before execution.`)
795
+ : ["Confirm allowlisted validation commands in the next deterministic implementation slice before execution."];
796
+ const approvalRequiredSteps = implementationRequest.approvalMode === "apply-capable"
797
+ ? [
798
+ "Any future patch application requires explicit approval before execution.",
799
+ "Any future build or validation execution requires approval after allowlist review."
800
+ ]
801
+ : ["The default path remains proposal-only; any patch or build execution requires a separate approved workflow."];
802
+ const risks = [
803
+ ...(implementationRequest.targetPaths.length === 0
804
+ ? ["Target paths were not supplied, so affected surfaces may still broaden after deterministic discovery."]
805
+ : []),
806
+ ...(implementationRequest.validationCommands.length === 0
807
+ ? ["Validation commands are not yet specified and will need deterministic allowlist confirmation later."]
808
+ : [])
809
+ ];
810
+ const openQuestions = [
811
+ ...(implementationRequest.constraints.length === 0
812
+ ? ["Which additional implementation constraints should be captured before execution work begins?"]
813
+ : []),
814
+ ...(designRecord.payload.policyChangesNeeded.length > 0
815
+ ? ["Do the policy surfaces identified in the design record require a separate approval review?"]
816
+ : [])
817
+ ];
818
+ const summary = `Implementation proposal prepared for ${implementationRequest.implementationGoal}.`;
819
+ const implementationProposal = implementationArtifactSchema.parse({
820
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/implementation.yaml", implementationRequest.designRecordRef], designRecord.source.issueRefs),
821
+ artifactKind: "implementation-proposal",
822
+ lifecycleDomain: "build",
823
+ workflow: {
824
+ name: state.workflow,
825
+ displayName: "Implementation Proposal"
826
+ },
827
+ payload: {
828
+ designRecordRef: implementationRequest.designRecordRef,
829
+ implementationGoal: implementationRequest.implementationGoal,
830
+ affectedPaths: finalAffectedPaths,
831
+ proposedChanges,
832
+ validationPlan,
833
+ approvalRequiredSteps,
834
+ risks,
835
+ openQuestions
836
+ }
837
+ });
838
+ return agentOutputSchema.parse({
839
+ summary,
840
+ findings: [],
841
+ proposedActions: [],
842
+ lifecycleArtifacts: [implementationProposal],
843
+ requestedTools: [],
844
+ blockedActionFlags: [],
845
+ confidence: 0.76,
846
+ metadata: {
847
+ deterministicInputs: {
848
+ targetPaths: implementationRequest.targetPaths,
849
+ validationCommands: implementationRequest.validationCommands,
850
+ constraints: implementationRequest.constraints,
851
+ designInterfaces: designRecord.payload.interfacesImpacted,
852
+ inventory: inventory ?? null
853
+ },
854
+ synthesizedProposal: {
855
+ affectedPaths: finalAffectedPaths,
856
+ approvalRequiredSteps,
857
+ openQuestions
858
+ }
859
+ }
860
+ });
861
+ }
862
+ };
863
+ const designAnalystAgent = {
864
+ manifest: agentManifestSchema.parse({
865
+ version: 1,
866
+ name: "design-analyst",
867
+ displayName: "Design Analyst",
868
+ category: "design",
869
+ runtime: {
870
+ minVersion: "0.1.0",
871
+ kind: "reasoning"
872
+ },
873
+ permissions: {
874
+ model: true,
875
+ network: false,
876
+ tools: [],
877
+ readPaths: ["**/*"],
878
+ writePaths: []
879
+ },
880
+ inputs: ["workflowInputs", "repo", "changes", "agentResults"],
881
+ outputs: ["lifecycleArtifacts"],
882
+ contextPolicy: {
883
+ sections: ["workflowInputs", "repo", "changes", "agentResults"],
884
+ minimalContext: true
885
+ },
886
+ catalog: {
887
+ domain: "design",
888
+ supportLevel: "official",
889
+ maturity: "mvp",
890
+ trustScope: "official-core-only"
891
+ },
892
+ trust: {
893
+ tier: "core",
894
+ source: "official",
895
+ reviewed: true
896
+ }
897
+ }),
898
+ outputSchema: agentOutputSchema,
899
+ async execute({ state, stateSlice }) {
900
+ const designRequest = getWorkflowInput(stateSlice, "designRequest");
901
+ const planningBrief = getWorkflowInput(stateSlice, "planningBrief");
902
+ const requestFile = getWorkflowInput(stateSlice, "requestFile");
903
+ if (!designRequest || !planningBrief) {
904
+ throw new Error("architecture-design-review requires validated design inputs before design analysis.");
905
+ }
906
+ const inventoryMetadata = isRecord(stateSlice.agentResults?.inventory?.metadata) ? stateSlice.agentResults.inventory.metadata : {};
907
+ const impactedInterfaces = asStringArray(inventoryMetadata.impactedInterfaces);
908
+ const schemaSurfaces = asStringArray(inventoryMetadata.schemaSurfaces);
909
+ const policySurfaces = asStringArray(inventoryMetadata.policySurfaces);
910
+ const optionsConsidered = designRequest.alternatives.length > 0
911
+ ? designRequest.alternatives.map((option) => ({
912
+ option,
913
+ summary: `Evaluate ${option} against the validated planning brief and bounded repository inventory.`
914
+ }))
915
+ : [
916
+ {
917
+ option: "single-workflow-pass",
918
+ summary: "Keep the workflow narrow by validating intake, inventory, and design analysis in one local pass."
919
+ },
920
+ {
921
+ option: "split-manual-design-doc",
922
+ summary: "Rely on ad hoc notes outside the workflow and treat design as manual-only."
923
+ }
924
+ ];
925
+ const chosenApproach = optionsConsidered[0]?.option ?? "single-workflow-pass";
926
+ const followUpWork = [
927
+ "Translate the accepted design into bounded implementation issues before coding begins.",
928
+ "Use the deterministic inventory to drive follow-up schema, policy, or interface validation.",
929
+ "Keep the planning brief and design record linked when implementation and QA workflows land."
930
+ ];
931
+ const summary = `Design record prepared for ${designRequest.decisionTarget}.`;
932
+ const designRecord = designArtifactSchema.parse({
933
+ ...buildArtifactEnvelopeBase(state, summary, [requestFile ?? ".agentops/requests/design.yaml", designRequest.planningBriefRef], planningBrief.source.issueRefs),
934
+ artifactKind: "design-record",
935
+ lifecycleDomain: "design",
936
+ workflow: {
937
+ name: state.workflow,
938
+ displayName: "Architecture And Design Review"
939
+ },
940
+ payload: {
941
+ decisionSummary: designRequest.decisionTarget,
942
+ context: `Planning brief summary: ${planningBrief.summary}`,
943
+ optionsConsidered,
944
+ chosenApproach,
945
+ tradeOffs: [
946
+ "Keeping the workflow local-first limits external specification ingestion in the MVP.",
947
+ "Deterministic inventory remains heuristic until deeper static-analysis surfaces land."
948
+ ],
949
+ risks: [
950
+ ...(impactedInterfaces.length === 0 ? ["No impacted interfaces were identified from the provided path hints."] : []),
951
+ ...(schemaSurfaces.length === 0 ? ["Schema touch points may still need manual confirmation."] : [])
952
+ ],
953
+ followUpWork,
954
+ interfacesImpacted: impactedInterfaces,
955
+ schemaChangesNeeded: schemaSurfaces,
956
+ policyChangesNeeded: policySurfaces,
957
+ migrationNotes: [],
958
+ compatibilityNotes: ["Requires a valid planning-brief bundle reference from planning-discovery."]
959
+ }
960
+ });
961
+ return agentOutputSchema.parse({
962
+ summary,
963
+ findings: [],
964
+ proposedActions: [],
965
+ lifecycleArtifacts: [designRecord],
966
+ requestedTools: [],
967
+ blockedActionFlags: [],
968
+ confidence: 0.78,
969
+ metadata: {
970
+ deterministicInventory: {
971
+ impactedInterfaces,
972
+ schemaSurfaces,
973
+ policySurfaces
974
+ },
975
+ synthesizedDecision: {
976
+ chosenApproach,
977
+ followUpWork
978
+ }
979
+ }
980
+ });
981
+ }
982
+ };
52
983
  const codeReviewAgent = {
53
984
  manifest: agentManifestSchema.parse({
54
985
  version: 1,
@@ -72,6 +1003,12 @@ const codeReviewAgent = {
72
1003
  sections: ["repo", "changes", "agentResults"],
73
1004
  minimalContext: true
74
1005
  },
1006
+ catalog: {
1007
+ domain: "review",
1008
+ supportLevel: "official",
1009
+ maturity: "mvp",
1010
+ trustScope: "official-core-only"
1011
+ },
75
1012
  trust: {
76
1013
  tier: "core",
77
1014
  source: "official",
@@ -158,6 +1095,12 @@ const securityAuditAgent = {
158
1095
  sections: ["repo", "changes", "policy"],
159
1096
  minimalContext: true
160
1097
  },
1098
+ catalog: {
1099
+ domain: "security",
1100
+ supportLevel: "official",
1101
+ maturity: "mvp",
1102
+ trustScope: "official-core-only"
1103
+ },
161
1104
  trust: {
162
1105
  tier: "core",
163
1106
  source: "official",
@@ -222,6 +1165,12 @@ const testGenerationAgent = {
222
1165
  sections: ["changes"],
223
1166
  minimalContext: true
224
1167
  },
1168
+ catalog: {
1169
+ domain: "test",
1170
+ supportLevel: "official",
1171
+ maturity: "mvp",
1172
+ trustScope: "official-core-only"
1173
+ },
225
1174
  trust: {
226
1175
  tier: "core",
227
1176
  source: "official",
@@ -263,6 +1212,15 @@ const testGenerationAgent = {
263
1212
  export function createBuiltinAgentRegistry() {
264
1213
  return new Map([
265
1214
  ["context-collector", contextCollectorAgent],
1215
+ ["planning-intake", planningIntakeAgent],
1216
+ ["planning-analyst", planningAnalystAgent],
1217
+ ["design-intake", designIntakeAgent],
1218
+ ["design-inventory", designInventoryAgent],
1219
+ ["implementation-intake", implementationIntakeAgent],
1220
+ ["qa-intake", qaIntakeAgent],
1221
+ ["implementation-inventory", implementationInventoryAgent],
1222
+ ["implementation-planner", implementationPlannerAgent],
1223
+ ["design-analyst", designAnalystAgent],
266
1224
  ["code-review", codeReviewAgent],
267
1225
  ["security-audit", securityAuditAgent],
268
1226
  ["test-generation", testGenerationAgent]