@fro.bot/systematic 3.15.0 → 3.16.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/cli.js CHANGED
@@ -1,20 +1,53 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
3
  import {
4
+ parseFrontmatter,
5
+ parse,
6
+ parseTree,
7
+ modify,
4
8
  applyEdits,
5
- discoverSkills,
6
- exports_external,
9
+ _isoDateTime,
10
+ _isoDate,
11
+ _isoTime,
12
+ _isoDuration,
13
+ string,
14
+ ZodISODateTime,
15
+ ZodISODate,
16
+ ZodISOTime,
17
+ ZodISODuration,
18
+ number,
19
+ boolean,
20
+ array,
21
+ object,
22
+ union,
23
+ discriminatedUnion,
24
+ _enum,
25
+ literal,
7
26
  extractString,
27
+ toSourcedPiSubagentsOverlays,
28
+ resolveRouting,
29
+ loadConfigWithSources,
30
+ collectRoutingTargets,
31
+ getConfigPaths,
8
32
  findAgentsInDir,
9
33
  findCommandsInDir,
10
34
  findSkillsInDir,
11
- getConfigPaths,
12
- loadConfigWithSources,
13
- modify,
14
- parse,
15
- parseFrontmatter,
16
- parseTree
17
- } from "./index-0stf3ag0.js";
35
+ discoverSkills
36
+ } from "./index-ae6mhwth.js";
37
+ var __defProp = Object.defineProperty;
38
+ var __returnValue = (v) => v;
39
+ function __exportSetter(name, newValue) {
40
+ this[name] = __returnValue.bind(null, newValue);
41
+ }
42
+ var __export = (target, all) => {
43
+ for (var name in all)
44
+ __defProp(target, name, {
45
+ get: all[name],
46
+ enumerable: true,
47
+ configurable: true,
48
+ set: __exportSetter.bind(all, name)
49
+ });
50
+ };
18
51
 
19
52
  // src/cli.ts
20
53
  import fs6 from "fs";
@@ -29,7 +62,15 @@ function buildAgentCatalog(agentsDir) {
29
62
  const entries = [];
30
63
  for (const info of infos) {
31
64
  const content = fs.readFileSync(info.file, "utf8");
32
- const entry = parseValidatedAgentEntry(content, info.file);
65
+ const parsed = parseValidatedAgentEntry(content, info.file);
66
+ const category = info.category ?? "";
67
+ const key = info.name;
68
+ const entry = {
69
+ ...parsed,
70
+ key,
71
+ category,
72
+ id: category ? `${category}/${key}` : key
73
+ };
33
74
  try {
34
75
  resolveToolAllowlist(entry.toolsSource);
35
76
  } catch (error) {
@@ -38,9 +79,9 @@ function buildAgentCatalog(agentsDir) {
38
79
  }
39
80
  const existing = byName.get(entry.name);
40
81
  if (existing) {
41
- existing.push(info.category ?? "(root)");
82
+ existing.push(category);
42
83
  } else {
43
- byName.set(entry.name, [info.category ?? "(root)"]);
84
+ byName.set(entry.name, [category]);
44
85
  }
45
86
  entries.push(entry);
46
87
  }
@@ -144,14 +185,19 @@ var CONFIG_AUTHORITY_FIELD_PATHS = [
144
185
  ];
145
186
  var CONFIG_PROTECTED_FIELD_PATHS = [
146
187
  "workflow_guard",
188
+ "profiles",
147
189
  "agents.*.model",
148
190
  "agents.*.permission",
149
191
  "agents.*.skills",
150
192
  "agents.*.variant",
193
+ "agents.*.opencode",
194
+ "agents.*.pi",
151
195
  "categories.*.model",
152
196
  "categories.*.permission",
153
197
  "categories.*.skills",
154
- "categories.*.variant"
198
+ "categories.*.variant",
199
+ "categories.*.opencode",
200
+ "categories.*.pi"
155
201
  ];
156
202
  var CONFIG_SOURCE_ERROR_CODES = [
157
203
  "parse-failed",
@@ -409,7 +455,14 @@ function configErrorCode(errorCode) {
409
455
  }
410
456
  function normalizeConfigObservation(config) {
411
457
  assertRecord(config, "config");
412
- assertAllowedKeys(config, ["authorities", "protectedFields", "sources"], "config");
458
+ assertAllowedKeys(config, [
459
+ "authorities",
460
+ "protectedFields",
461
+ "sources",
462
+ "activeProfile",
463
+ "profileSelectorSource",
464
+ "profileFallback"
465
+ ], "config");
413
466
  if (!Array.isArray(config.sources)) {
414
467
  throw new TypeError("config.sources must be an array");
415
468
  }
@@ -778,7 +831,30 @@ function buildCapabilitySnapshot(options) {
778
831
  options.outputSink?.(serialized);
779
832
  return snapshot;
780
833
  }
781
-
834
+ // node_modules/.bun/zod@4.5.4/node_modules/zod/v4/classic/iso.js
835
+ var exports_iso = {};
836
+ __export(exports_iso, {
837
+ ZodISODate: () => ZodISODate,
838
+ ZodISODateTime: () => ZodISODateTime,
839
+ ZodISODuration: () => ZodISODuration,
840
+ ZodISOTime: () => ZodISOTime,
841
+ date: () => date,
842
+ datetime: () => datetime,
843
+ duration: () => duration,
844
+ time: () => time
845
+ });
846
+ function datetime(params) {
847
+ return _isoDateTime(ZodISODateTime, params);
848
+ }
849
+ function date(params) {
850
+ return _isoDate(ZodISODate, params);
851
+ }
852
+ function time(params) {
853
+ return _isoTime(ZodISOTime, params);
854
+ }
855
+ function duration(params) {
856
+ return _isoDuration(ZodISODuration, params);
857
+ }
782
858
  // src/lib/pi-subagents-export.ts
783
859
  import * as crypto2 from "crypto";
784
860
  import * as fs3 from "fs";
@@ -1424,10 +1500,10 @@ function ownedFilenames(manifest) {
1424
1500
  function parseSourceCategoryAndKey(sourceRelPath) {
1425
1501
  const parts = sourceRelPath.split("/");
1426
1502
  if (parts.length >= 3 && parts[0] === "agents") {
1427
- const fileName2 = parts.at(-1) ?? "";
1503
+ const fileName = parts.at(-1) ?? "";
1428
1504
  return {
1429
1505
  category: parts[1],
1430
- agentKey: fileName2.replace(/\.md$/, "")
1506
+ agentKey: fileName.replace(/\.md$/, "")
1431
1507
  };
1432
1508
  }
1433
1509
  const fileName = parts.at(-1) ?? sourceRelPath;
@@ -1443,21 +1519,19 @@ function lookupOverlay(map, category, agentKey) {
1443
1519
  }
1444
1520
  return map[agentKey];
1445
1521
  }
1446
- function resolveModel(config, category, agentKey) {
1447
- const agentOverlay = lookupOverlay(config.agents, category, agentKey);
1448
- if (agentOverlay && Object.hasOwn(agentOverlay, "model")) {
1449
- const value = agentOverlay.model;
1450
- return typeof value === "string" ? value : undefined;
1451
- }
1452
- const categoryOverlay = category ? config.categories?.[category] : undefined;
1453
- if (categoryOverlay && Object.hasOwn(categoryOverlay, "model")) {
1454
- const value = categoryOverlay.model;
1455
- return typeof value === "string" ? value : undefined;
1456
- }
1457
- return;
1522
+ function resolveRoutedFields(overlays, piSubagentsOverlays, category, agentKey) {
1523
+ const resolution = resolveRouting({
1524
+ overlays,
1525
+ piSubagentsOverlays,
1526
+ target: { agentKey, category: category ?? "" },
1527
+ harness: "pi"
1528
+ });
1529
+ return {
1530
+ model: typeof resolution.model === "string" ? resolution.model : undefined,
1531
+ thinking: resolution.qualifier
1532
+ };
1458
1533
  }
1459
- var PI_SUBAGENTS_FRONTMATTER_FIELDS = [
1460
- "thinking",
1534
+ var OTHER_PI_SUBAGENTS_FRONTMATTER_FIELDS = [
1461
1535
  "max_turns",
1462
1536
  "tools",
1463
1537
  "skills"
@@ -1466,7 +1540,7 @@ function resolvePiSubagentsFields(config, category, agentKey) {
1466
1540
  const categoryOverlay = category ? config.pi_subagents?.categories?.[category] : undefined;
1467
1541
  const agentOverlay = lookupOverlay(config.pi_subagents?.agents, category, agentKey);
1468
1542
  const result = {};
1469
- for (const field of PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1543
+ for (const field of OTHER_PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1470
1544
  if (agentOverlay && Object.hasOwn(agentOverlay, field)) {
1471
1545
  result[field] = agentOverlay[field];
1472
1546
  } else if (categoryOverlay && Object.hasOwn(categoryOverlay, field)) {
@@ -1475,19 +1549,21 @@ function resolvePiSubagentsFields(config, category, agentKey) {
1475
1549
  }
1476
1550
  return result;
1477
1551
  }
1478
- function applyConfigToContent(content, config, category, agentKey) {
1552
+ function applyConfigToContent(content, config, overlays, piSubagentsOverlays, category, agentKey) {
1479
1553
  const { data, body } = parseFrontmatter(content);
1480
- const model = resolveModel(config, category, agentKey);
1481
- const piSubagentsFields = resolvePiSubagentsFields(config, category, agentKey);
1554
+ const { model, thinking } = resolveRoutedFields(overlays, piSubagentsOverlays, category, agentKey);
1555
+ const otherFields = resolvePiSubagentsFields(config, category, agentKey);
1482
1556
  const frontmatter = {};
1483
1557
  if (typeof data.description === "string") {
1484
1558
  frontmatter.description = data.description;
1485
1559
  }
1486
1560
  if (model !== undefined)
1487
1561
  frontmatter.model = model;
1488
- for (const field of PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1489
- if (Object.hasOwn(piSubagentsFields, field)) {
1490
- frontmatter[field] = piSubagentsFields[field];
1562
+ if (thinking !== undefined)
1563
+ frontmatter.thinking = thinking;
1564
+ for (const field of OTHER_PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1565
+ if (Object.hasOwn(otherFields, field)) {
1566
+ frontmatter[field] = otherFields[field];
1491
1567
  }
1492
1568
  }
1493
1569
  let frontmatterBlock;
@@ -1506,12 +1582,12 @@ ${lines}
1506
1582
 
1507
1583
  ${trimmedBody}`;
1508
1584
  }
1509
- function applyConfigToEntries(entries, config) {
1585
+ function applyConfigToEntries(entries, config, overlays, piSubagentsOverlays) {
1510
1586
  return entries.map((entry) => {
1511
1587
  if (entry.status === "excluded-critical" || !entry.content)
1512
1588
  return entry;
1513
1589
  const { category, agentKey } = parseSourceCategoryAndKey(entry.sourceRelPath);
1514
- const content = applyConfigToContent(entry.content, config, category, agentKey);
1590
+ const content = applyConfigToContent(entry.content, config, overlays, piSubagentsOverlays, category, agentKey);
1515
1591
  const hash = crypto2.createHash("sha256").update(content).digest("hex");
1516
1592
  return { ...entry, content, hash };
1517
1593
  });
@@ -1520,10 +1596,11 @@ function generateEntries(repoRoot, configOptions) {
1520
1596
  const entries = generateAll(repoRoot);
1521
1597
  if (!configOptions)
1522
1598
  return entries;
1523
- const config = loadConfigWithSources(configOptions.cwd, {
1599
+ const { config, overlays } = loadConfigWithSources(configOptions.cwd, {
1524
1600
  includeProject: configOptions.scope === "project"
1525
- }).config;
1526
- return applyConfigToEntries(entries, config);
1601
+ });
1602
+ const piSubagentsOverlays = toSourcedPiSubagentsOverlays(config.pi_subagents);
1603
+ return applyConfigToEntries(entries, config, overlays, piSubagentsOverlays);
1527
1604
  }
1528
1605
  function findPackageRoot() {
1529
1606
  const thisFile = fileURLToPath(import.meta.url);
@@ -1949,7 +2026,7 @@ function cleanup(agentsRoot, configOptions) {
1949
2026
  ...targets.map((t) => t.path),
1950
2027
  ...manifestPath ? [manifestPath] : []
1951
2028
  ];
1952
- const tx2 = runWithRollback(pathsToWatch, [
2029
+ const tx = runWithRollback(pathsToWatch, [
1953
2030
  ...targets.map(({ path: p, filename, hash }) => () => {
1954
2031
  if (fs3.existsSync(p)) {
1955
2032
  assertHashMatchesOrThrow(p, hash, filename, "delete");
@@ -1963,7 +2040,7 @@ function cleanup(agentsRoot, configOptions) {
1963
2040
  }
1964
2041
  ] : []
1965
2042
  ]);
1966
- return { kind: "tx", tx: tx2 };
2043
+ return { kind: "tx", tx };
1967
2044
  });
1968
2045
  if (!lockResult.locked) {
1969
2046
  if (lockResult.isContention)
@@ -2010,8 +2087,8 @@ function hasParentDirectoryTraversal(input) {
2010
2087
  }
2011
2088
  function pathContainsSymlink(candidate) {
2012
2089
  let current = path3.parse(candidate).root;
2013
- const relative2 = path3.relative(current, candidate);
2014
- for (const segment of relative2.split(path3.sep)) {
2090
+ const relative = path3.relative(current, candidate);
2091
+ for (const segment of relative.split(path3.sep)) {
2015
2092
  if (!segment)
2016
2093
  continue;
2017
2094
  current = path3.join(current, segment);
@@ -2025,8 +2102,8 @@ function pathContainsSymlink(candidate) {
2025
2102
  return false;
2026
2103
  }
2027
2104
  function isWithinDirectory(candidate, directory) {
2028
- const relative2 = path3.relative(directory, candidate);
2029
- return relative2 !== "" && relative2 !== ".." && !relative2.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relative2);
2105
+ const relative = path3.relative(directory, candidate);
2106
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relative);
2030
2107
  }
2031
2108
  function resolveReviewArtifactPath(input, cwd) {
2032
2109
  if (hasParentDirectoryTraversal(input)) {
@@ -2096,14 +2173,14 @@ var REVIEW_ARTIFACT_CUSTOM_MESSAGES = [
2096
2173
  "unsatisfied risk coverage must not cite an input finding ID",
2097
2174
  "passed validation must not include a reason; non-passed validation requires a reason"
2098
2175
  ];
2099
- var boundedText = (maxLength) => exports_external.string().min(1).max(maxLength).regex(/\S/);
2100
- var DispatchOutcomeSchema = exports_external.enum([
2176
+ var boundedText = (maxLength) => string().min(1).max(maxLength).regex(/\S/);
2177
+ var DispatchOutcomeSchema = _enum([
2101
2178
  "findings",
2102
2179
  "empty",
2103
2180
  "malformed",
2104
2181
  "never_returned"
2105
2182
  ]);
2106
- var DispositionSchema = exports_external.enum([
2183
+ var DispositionSchema = _enum([
2107
2184
  "surviving",
2108
2185
  "merged",
2109
2186
  "suppressed",
@@ -2111,12 +2188,12 @@ var DispositionSchema = exports_external.enum([
2111
2188
  "rejected"
2112
2189
  ]);
2113
2190
  var AdmittedDispositionSchema = DispositionSchema.exclude(["rejected"]);
2114
- var HarnessSchema = exports_external.enum(["opencode", "pi", "claude-code"]);
2191
+ var HarnessSchema = _enum(["opencode", "pi", "claude-code"]);
2115
2192
  var RepoRelativePathSchema = boundedText(256).regex(/^(?!\/)(?![A-Za-z]:[\\/])(?!\\).+/);
2116
2193
  var ReviewerSchema = boundedText(MAX_REVIEWER_LENGTH);
2117
- var BranchSchema = exports_external.string().max(MAX_BRANCH_LENGTH);
2118
- var HeadShaSchema = exports_external.string().regex(/^[0-9a-f]{40}$/);
2119
- var CompletedAtSchema = exports_external.iso.datetime({ offset: false });
2194
+ var BranchSchema = string().max(MAX_BRANCH_LENGTH);
2195
+ var HeadShaSchema = string().regex(/^[0-9a-f]{40}$/);
2196
+ var CompletedAtSchema = exports_iso.datetime({ offset: false });
2120
2197
  var ReasonSchema = boundedText(MAX_REASON_LENGTH);
2121
2198
  var RISK_CRITICAL_PERSONAS = [
2122
2199
  "security",
@@ -2125,42 +2202,42 @@ var RISK_CRITICAL_PERSONAS = [
2125
2202
  "reliability",
2126
2203
  "performance"
2127
2204
  ];
2128
- var RiskCriticalPersonaSchema = exports_external.enum(RISK_CRITICAL_PERSONAS);
2205
+ var RiskCriticalPersonaSchema = _enum(RISK_CRITICAL_PERSONAS);
2129
2206
  var FindingTitleSchema = boundedText(256);
2130
- var SeveritySchema = exports_external.enum(["P0", "P1", "P2", "P3", "unknown"]);
2207
+ var SeveritySchema = _enum(["P0", "P1", "P2", "P3", "unknown"]);
2131
2208
  var FindingSeveritySchema = SeveritySchema.exclude(["unknown"]);
2132
- var AutofixClassSchema = exports_external.enum([
2209
+ var AutofixClassSchema = _enum([
2133
2210
  "safe_auto",
2134
2211
  "gated_auto",
2135
2212
  "manual",
2136
2213
  "advisory"
2137
2214
  ]);
2138
- var OwnerSchema = exports_external.enum([
2215
+ var OwnerSchema = _enum([
2139
2216
  "review-fixer",
2140
2217
  "downstream-resolver",
2141
2218
  "human",
2142
2219
  "release"
2143
2220
  ]);
2144
2221
  var BoundedEvidenceStringSchema = boundedText(500).regex(/^(?!\/)(?![A-Za-z]:[\\/])(?!\\).+/);
2145
- var OverflowEvidenceSchema = exports_external.object({
2146
- overflow: exports_external.literal(true),
2222
+ var OverflowEvidenceSchema = object({
2223
+ overflow: literal(true),
2147
2224
  excerpt: BoundedEvidenceStringSchema
2148
2225
  }).strict();
2149
- var EvidenceSchema = exports_external.array(exports_external.union([BoundedEvidenceStringSchema, OverflowEvidenceSchema])).min(1).max(5);
2150
- var AdmittedInputFindingSchema = exports_external.object({
2151
- record_type: exports_external.literal("admitted"),
2226
+ var EvidenceSchema = array(union([BoundedEvidenceStringSchema, OverflowEvidenceSchema])).min(1).max(5);
2227
+ var AdmittedInputFindingSchema = object({
2228
+ record_type: literal("admitted"),
2152
2229
  input_id: boundedText(MAX_INPUT_ID_LENGTH),
2153
2230
  reviewer: ReviewerSchema,
2154
- confidence: exports_external.number().min(0).max(1),
2231
+ confidence: number().min(0).max(1),
2155
2232
  disposition: AdmittedDispositionSchema,
2156
2233
  reason: ReasonSchema
2157
2234
  }).strict();
2158
- var RejectedInputFindingSchema = exports_external.object({
2159
- record_type: exports_external.literal("rejected_summary"),
2235
+ var RejectedInputFindingSchema = object({
2236
+ record_type: literal("rejected_summary"),
2160
2237
  reviewer: ReviewerSchema,
2161
2238
  dispatch_outcome: DispatchOutcomeSchema,
2162
- rejected_finding_count: exports_external.number().int().positive().max(MAX_FINDINGS),
2163
- rejected_severities: exports_external.array(SeveritySchema).max(MAX_FINDINGS),
2239
+ rejected_finding_count: number().int().positive().max(MAX_FINDINGS),
2240
+ rejected_severities: array(SeveritySchema).max(MAX_FINDINGS),
2164
2241
  disposition: DispositionSchema.extract(["rejected"]),
2165
2242
  reason: ReasonSchema
2166
2243
  }).strict().superRefine((row, ctx) => {
@@ -2172,31 +2249,31 @@ var RejectedInputFindingSchema = exports_external.object({
2172
2249
  });
2173
2250
  }
2174
2251
  });
2175
- var InputFindingSchema = exports_external.discriminatedUnion("record_type", [
2252
+ var InputFindingSchema = discriminatedUnion("record_type", [
2176
2253
  AdmittedInputFindingSchema,
2177
2254
  RejectedInputFindingSchema
2178
2255
  ]);
2179
- var ProvenanceSchema = exports_external.object({
2256
+ var ProvenanceSchema = object({
2180
2257
  fingerprint: boundedText(512),
2181
- submitters: exports_external.array(ReviewerSchema).max(MAX_PERSONAS),
2182
- agreement_credit: exports_external.array(ReviewerSchema).max(MAX_PERSONAS)
2258
+ submitters: array(ReviewerSchema).max(MAX_PERSONAS),
2259
+ agreement_credit: array(ReviewerSchema).max(MAX_PERSONAS)
2183
2260
  }).strict();
2184
- var SynthesizedFindingFieldsSchema = exports_external.object({
2261
+ var SynthesizedFindingFieldsSchema = object({
2185
2262
  title: FindingTitleSchema,
2186
2263
  severity: FindingSeveritySchema,
2187
2264
  file: RepoRelativePathSchema,
2188
- line: exports_external.number().int().positive(),
2265
+ line: number().int().positive(),
2189
2266
  why_it_matters: boundedText(2048),
2190
2267
  autofix_class: AutofixClassSchema,
2191
2268
  owner: OwnerSchema,
2192
- requires_verification: exports_external.boolean(),
2193
- confidence: exports_external.number().min(0).max(1),
2269
+ requires_verification: boolean(),
2270
+ confidence: number().min(0).max(1),
2194
2271
  evidence: EvidenceSchema,
2195
- pre_existing: exports_external.boolean(),
2196
- suggested_fix: exports_external.string().max(2048).nullable().optional(),
2197
- validated: exports_external.boolean().optional(),
2272
+ pre_existing: boolean(),
2273
+ suggested_fix: string().max(2048).nullable().optional(),
2274
+ validated: boolean().optional(),
2198
2275
  validation_reason: ReasonSchema.optional(),
2199
- input_finding_ids: exports_external.array(boundedText(MAX_INPUT_ID_LENGTH)).min(1).max(MAX_FINDINGS),
2276
+ input_finding_ids: array(boundedText(MAX_INPUT_ID_LENGTH)).min(1).max(MAX_FINDINGS),
2200
2277
  provenance: ProvenanceSchema
2201
2278
  }).strict();
2202
2279
  var SynthesizedFindingSchema = SynthesizedFindingFieldsSchema.superRefine((finding, ctx) => {
@@ -2208,12 +2285,12 @@ var SynthesizedFindingSchema = SynthesizedFindingFieldsSchema.superRefine((findi
2208
2285
  });
2209
2286
  }
2210
2287
  });
2211
- var DispatchSchema = exports_external.object({
2288
+ var DispatchSchema = object({
2212
2289
  persona: ReviewerSchema,
2213
2290
  dispatch_outcome: DispatchOutcomeSchema,
2214
- input_finding_count: exports_external.number().int().nonnegative().max(MAX_FINDINGS),
2291
+ input_finding_count: number().int().nonnegative().max(MAX_FINDINGS),
2215
2292
  rejection_reason: ReasonSchema.optional(),
2216
- selection_surface: exports_external.array(RepoRelativePathSchema).max(MAX_FINDINGS).optional(),
2293
+ selection_surface: array(RepoRelativePathSchema).max(MAX_FINDINGS).optional(),
2217
2294
  selection_reason: ReasonSchema.optional()
2218
2295
  }).strict().superRefine((dispatch, ctx) => {
2219
2296
  if (RISK_CRITICAL_PERSONAS.includes(dispatch.persona) && (!dispatch.selection_surface || dispatch.selection_surface.length === 0)) {
@@ -2224,29 +2301,29 @@ var DispatchSchema = exports_external.object({
2224
2301
  });
2225
2302
  }
2226
2303
  });
2227
- var DispositionCountsSchema = exports_external.object({
2228
- surviving: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2229
- merged: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2230
- suppressed: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2231
- filtered: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2232
- rejected: exports_external.number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS)
2304
+ var DispositionCountsSchema = object({
2305
+ surviving: number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2306
+ merged: number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2307
+ suppressed: number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2308
+ filtered: number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS),
2309
+ rejected: number().int().nonnegative().max(MAX_FINDINGS * MAX_PERSONAS)
2233
2310
  }).strict();
2234
- var CoverageSchema = exports_external.object({
2235
- reviewers: exports_external.number().int().nonnegative().max(MAX_PERSONAS).optional(),
2236
- validators: exports_external.number().int().nonnegative().max(MAX_PERSONAS).optional(),
2237
- residual_risks: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2238
- testing_gaps: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2239
- failed_reviewers: exports_external.array(ReviewerSchema).max(MAX_PERSONAS),
2240
- validator_failures: exports_external.array(ReasonSchema).max(MAX_PERSONAS),
2241
- intent_uncertainty: exports_external.array(ReasonSchema).max(MAX_PERSONAS)
2311
+ var CoverageSchema = object({
2312
+ reviewers: number().int().nonnegative().max(MAX_PERSONAS).optional(),
2313
+ validators: number().int().nonnegative().max(MAX_PERSONAS).optional(),
2314
+ residual_risks: array(ReasonSchema).max(MAX_PERSONAS),
2315
+ testing_gaps: array(ReasonSchema).max(MAX_PERSONAS),
2316
+ failed_reviewers: array(ReviewerSchema).max(MAX_PERSONAS),
2317
+ validator_failures: array(ReasonSchema).max(MAX_PERSONAS),
2318
+ intent_uncertainty: array(ReasonSchema).max(MAX_PERSONAS)
2242
2319
  }).strict();
2243
- var DeclinedMergeSchema = exports_external.object({
2320
+ var DeclinedMergeSchema = object({
2244
2321
  file: RepoRelativePathSchema,
2245
- input_finding_ids: exports_external.array(boundedText(MAX_INPUT_ID_LENGTH)).min(2).max(MAX_FINDINGS),
2322
+ input_finding_ids: array(boundedText(MAX_INPUT_ID_LENGTH)).min(2).max(MAX_FINDINGS),
2246
2323
  reason: ReasonSchema
2247
2324
  }).strict();
2248
- var ValidationSchema = exports_external.object({
2249
- status: exports_external.enum([
2325
+ var ValidationSchema = object({
2326
+ status: _enum([
2250
2327
  "passed",
2251
2328
  "failed",
2252
2329
  "unavailable",
@@ -2269,9 +2346,9 @@ var ValidationSchema = exports_external.object({
2269
2346
  });
2270
2347
  }
2271
2348
  });
2272
- var RiskCoverageSchema = exports_external.object({
2349
+ var RiskCoverageSchema = object({
2273
2350
  persona: RiskCriticalPersonaSchema,
2274
- satisfied: exports_external.boolean(),
2351
+ satisfied: boolean(),
2275
2352
  input_finding_id: boundedText(MAX_INPUT_ID_LENGTH).optional()
2276
2353
  }).strict().superRefine((coverage, ctx) => {
2277
2354
  if (coverage.satisfied && coverage.input_finding_id === undefined) {
@@ -2289,14 +2366,14 @@ var RiskCoverageSchema = exports_external.object({
2289
2366
  });
2290
2367
  }
2291
2368
  });
2292
- var ReviewArtifactSchema = exports_external.object({
2293
- schema_version: exports_external.literal(1),
2369
+ var ReviewArtifactSchema = object({
2370
+ schema_version: literal(1),
2294
2371
  run_id: boundedText(MAX_RUN_ID_LENGTH),
2295
2372
  branch: BranchSchema,
2296
2373
  head_sha: HeadShaSchema,
2297
- mode: exports_external.enum(["interactive", "autofix", "headless"]),
2374
+ mode: _enum(["interactive", "autofix", "headless"]),
2298
2375
  harness: HarnessSchema,
2299
- run_status: exports_external.enum([
2376
+ run_status: _enum([
2300
2377
  "in_progress",
2301
2378
  "completed",
2302
2379
  "degraded",
@@ -2304,15 +2381,15 @@ var ReviewArtifactSchema = exports_external.object({
2304
2381
  ]),
2305
2382
  verdict: boundedText(256),
2306
2383
  completed_at: CompletedAtSchema,
2307
- dispatches: exports_external.array(DispatchSchema).max(MAX_PERSONAS),
2308
- input_findings: exports_external.array(InputFindingSchema).max(MAX_FINDINGS * MAX_PERSONAS),
2309
- findings: exports_external.array(SynthesizedFindingSchema).max(MAX_FINDINGS),
2310
- declined_merges: exports_external.array(DeclinedMergeSchema).max(MAX_FINDINGS).optional(),
2311
- risk_coverage: exports_external.array(RiskCoverageSchema).max(MAX_PERSONAS).optional(),
2384
+ dispatches: array(DispatchSchema).max(MAX_PERSONAS),
2385
+ input_findings: array(InputFindingSchema).max(MAX_FINDINGS * MAX_PERSONAS),
2386
+ findings: array(SynthesizedFindingSchema).max(MAX_FINDINGS),
2387
+ declined_merges: array(DeclinedMergeSchema).max(MAX_FINDINGS).optional(),
2388
+ risk_coverage: array(RiskCoverageSchema).max(MAX_PERSONAS).optional(),
2312
2389
  disposition_counts: DispositionCountsSchema,
2313
- applied_fixes: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2314
- residual_actionable_work: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2315
- advisory_outputs: exports_external.array(ReasonSchema).max(MAX_FINDINGS),
2390
+ applied_fixes: array(ReasonSchema).max(MAX_FINDINGS),
2391
+ residual_actionable_work: array(ReasonSchema).max(MAX_FINDINGS),
2392
+ advisory_outputs: array(ReasonSchema).max(MAX_FINDINGS),
2316
2393
  coverage: CoverageSchema,
2317
2394
  validation: ValidationSchema.optional()
2318
2395
  }).strict();
@@ -2365,8 +2442,8 @@ function lstatOrNull(targetPath) {
2365
2442
  function assertRealpathUnderCwd(dir, cwd) {
2366
2443
  const realDir = fs5.realpathSync(dir);
2367
2444
  const realCwd = fs5.realpathSync(cwd);
2368
- const relative2 = path4.relative(realCwd, realDir);
2369
- if (relative2.startsWith("..") || path4.isAbsolute(relative2)) {
2445
+ const relative = path4.relative(realCwd, realDir);
2446
+ if (relative.startsWith("..") || path4.isAbsolute(relative)) {
2370
2447
  throw createSetupError(`Refusing to write under ${dir}: resolved directory escapes the project root`);
2371
2448
  }
2372
2449
  }
@@ -2459,8 +2536,8 @@ function writeTempAndRename(destPath, content, mode, preserveExactMode, ops) {
2459
2536
  throw error;
2460
2537
  }
2461
2538
  }
2462
- function makeTempPath(parentDir, basename2) {
2463
- return path4.join(parentDir, `.${basename2}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2539
+ function makeTempPath(parentDir, basename) {
2540
+ return path4.join(parentDir, `.${basename}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2464
2541
  }
2465
2542
  function cleanupTemp(tempPath, ops) {
2466
2543
  if (!fs5.existsSync(tempPath))
@@ -2515,8 +2592,8 @@ function setupOpenCode(cwd, ops) {
2515
2592
  throw createSetupError(`Invalid OpenCode config in ${targetPath}: \`plugins\` (plural) is not supported by OpenCode's schema; use singular \`plugin\``);
2516
2593
  }
2517
2594
  if (!Object.hasOwn(parsed, "plugin")) {
2518
- const edits2 = modify(rawText, ["plugin"], [SYSTEMATIC_PACKAGE_NAME], {});
2519
- atomicWrite(targetPath, applyEdits(rawText, edits2), existing.bytes, existing.mode, ops);
2595
+ const edits = modify(rawText, ["plugin"], [SYSTEMATIC_PACKAGE_NAME], {});
2596
+ atomicWrite(targetPath, applyEdits(rawText, edits), existing.bytes, existing.mode, ops);
2520
2597
  return { status: "configured", targetPath };
2521
2598
  }
2522
2599
  const existingValue = parsed.plugin;
@@ -2577,8 +2654,8 @@ function setupPi(cwd, ops) {
2577
2654
  }
2578
2655
  const existingPackages = parsed.packages;
2579
2656
  if (existingPackages === undefined) {
2580
- const edits2 = modify(rawText, ["packages"], [PI_PACKAGE_IDENTIFIER], {});
2581
- atomicWrite(targetPath, applyEdits(rawText, edits2), existing.bytes, existing.mode, ops);
2657
+ const edits = modify(rawText, ["packages"], [PI_PACKAGE_IDENTIFIER], {});
2658
+ atomicWrite(targetPath, applyEdits(rawText, edits), existing.bytes, existing.mode, ops);
2582
2659
  return { status: "configured", targetPath };
2583
2660
  }
2584
2661
  if (!Array.isArray(existingPackages)) {
@@ -2650,7 +2727,7 @@ Commands:
2650
2727
  Exit statuses: 0 valid artifact, 1 validation failure,
2651
2728
  2 operational failure, 3 legacy artifact with no schema_version
2652
2729
  config [subcommand] Configuration management
2653
- show Show configuration
2730
+ show [--json] Show configuration (--json for a machine-readable resolved view)
2654
2731
  path Print config file locations
2655
2732
  setup --harness opencode|pi Configure a harness to load Systematic (project-local only)
2656
2733
  pi-subagents <subcommand> Manage Systematic persona export for pi-subagents
@@ -2669,6 +2746,7 @@ Examples:
2669
2746
  systematic validate-review-artifact .context/systematic/ce-review/review-summary.json
2670
2747
  systematic list agents
2671
2748
  systematic config show
2749
+ systematic config show --json
2672
2750
  systematic setup --harness opencode
2673
2751
  systematic setup --harness pi
2674
2752
  systematic pi-subagents preview
@@ -2677,6 +2755,13 @@ Examples:
2677
2755
  systematic pi-subagents refresh
2678
2756
  systematic pi-subagents cleanup
2679
2757
  `;
2758
+ var CONFIG_SHOW_USAGE = `Usage: systematic config show [--json]
2759
+
2760
+ Options:
2761
+ --json Print a machine-readable resolved view (active profile, fallback,
2762
+ and per-agent/harness routing) instead of prose. No raw file
2763
+ contents in JSON mode.
2764
+ `;
2680
2765
  var PI_SUBAGENTS_USAGE = `Usage: systematic pi-subagents <subcommand> [--scope project|global]
2681
2766
 
2682
2767
  Subcommands:
@@ -2952,7 +3037,130 @@ function listItems(type) {
2952
3037
  console.log(` ${item.name}`);
2953
3038
  }
2954
3039
  }
2955
- function configShow() {
3040
+ function formatRoutingModel(model) {
3041
+ if (model === undefined)
3042
+ return "(unset)";
3043
+ if (model === null)
3044
+ return "null (inherit)";
3045
+ return model;
3046
+ }
3047
+ function formatRoutingQualifier(qualifier) {
3048
+ return qualifier === undefined ? "(none)" : qualifier;
3049
+ }
3050
+ function formatRoutingSource(source) {
3051
+ return source === undefined ? "(n/a)" : `${source.level}/${source.form}`;
3052
+ }
3053
+ function printProfileSummary(metadata) {
3054
+ const activeLabel = metadata.activeProfile ?? "none";
3055
+ const selectedByLabel = metadata.profileSelectorSource ?? "(none)";
3056
+ console.log(` Active profile: ${activeLabel}`);
3057
+ console.log(` Selected by: ${selectedByLabel}`);
3058
+ if (metadata.profileFallback) {
3059
+ const usedLabel = metadata.profileFallback.usedDefault ? `your default profile "${metadata.profileFallback.usedDefault}"` : "base configuration (no default profile is set)";
3060
+ console.log(` Fallback: requested "${metadata.profileFallback.requested}" is not defined; used ${usedLabel}`);
3061
+ }
3062
+ }
3063
+ function disabledAgentsSet(loaded) {
3064
+ return new Set(loaded.config.disabled_agents ?? []);
3065
+ }
3066
+ function printRoutingTable(loaded) {
3067
+ const targets = collectRoutingTargets(loaded.overlays, disabledAgentsSet(loaded)).sort((left, right) => `${left.category}/${left.agentKey}`.localeCompare(`${right.category}/${right.agentKey}`));
3068
+ if (targets.length === 0) {
3069
+ console.log(" No per-agent or per-category overlays are defined.");
3070
+ return;
3071
+ }
3072
+ for (const target of targets) {
3073
+ console.log(` ${target.agentKey} (${target.category}):`);
3074
+ for (const harness of ["opencode", "pi"]) {
3075
+ const resolution = resolveRouting({
3076
+ harness,
3077
+ overlays: loaded.overlays,
3078
+ piSubagentsOverlays: loaded.piSubagentsOverlays,
3079
+ target
3080
+ });
3081
+ const qualifierName = harness === "opencode" ? "variant" : "thinking";
3082
+ console.log(` ${harness}: model=${formatRoutingModel(resolution.model)} (${formatRoutingSource(resolution.source.model)}), ` + `${qualifierName}=${formatRoutingQualifier(resolution.qualifier)} (${formatRoutingSource(resolution.source.qualifier)})`);
3083
+ }
3084
+ }
3085
+ }
3086
+ function printResolvedSection() {
3087
+ let loaded;
3088
+ try {
3089
+ loaded = loadConfigWithSources(process.cwd());
3090
+ } catch {
3091
+ console.log(`
3092
+ Resolved configuration: unavailable (the configuration failed to load).`);
3093
+ return;
3094
+ }
3095
+ const { metadata, overlays } = loaded;
3096
+ const hasProfileSelection = metadata.activeProfile !== null || metadata.profileSelectorSource !== null;
3097
+ const hasOverlays = Object.keys(overlays.agents).length > 0 || Object.keys(overlays.categories).length > 0;
3098
+ console.log(`
3099
+ Resolved configuration:`);
3100
+ if (!hasProfileSelection && !hasOverlays) {
3101
+ console.log(" No profiles are selected and no per-agent/category overlays are defined.");
3102
+ return;
3103
+ }
3104
+ printProfileSummary(metadata);
3105
+ console.log("");
3106
+ console.log(" Routing:");
3107
+ printRoutingTable(loaded);
3108
+ }
3109
+ function toJsonRoutingResolution(resolution) {
3110
+ return {
3111
+ model: resolution.model,
3112
+ qualifier: resolution.qualifier,
3113
+ source: resolution.source
3114
+ };
3115
+ }
3116
+ function buildConfigShowJson() {
3117
+ const paths = getConfigPaths(process.cwd());
3118
+ const locations = {
3119
+ user: paths.userConfig,
3120
+ project: paths.projectConfig,
3121
+ custom: paths.customConfig ?? null
3122
+ };
3123
+ let loaded;
3124
+ try {
3125
+ loaded = loadConfigWithSources(process.cwd());
3126
+ } catch (error) {
3127
+ return {
3128
+ locations,
3129
+ error: error instanceof Error ? error.message : String(error)
3130
+ };
3131
+ }
3132
+ const targets = collectRoutingTargets(loaded.overlays, disabledAgentsSet(loaded)).sort((left, right) => `${left.category}/${left.agentKey}`.localeCompare(`${right.category}/${right.agentKey}`));
3133
+ const routing = targets.map((target) => ({
3134
+ target,
3135
+ opencode: toJsonRoutingResolution(resolveRouting({
3136
+ harness: "opencode",
3137
+ overlays: loaded.overlays,
3138
+ piSubagentsOverlays: loaded.piSubagentsOverlays,
3139
+ target
3140
+ })),
3141
+ pi: toJsonRoutingResolution(resolveRouting({
3142
+ harness: "pi",
3143
+ overlays: loaded.overlays,
3144
+ piSubagentsOverlays: loaded.piSubagentsOverlays,
3145
+ target
3146
+ }))
3147
+ }));
3148
+ return {
3149
+ locations,
3150
+ activeProfile: loaded.metadata.activeProfile,
3151
+ profileSelectorSource: loaded.metadata.profileSelectorSource,
3152
+ profileFallback: loaded.metadata.profileFallback,
3153
+ routing
3154
+ };
3155
+ }
3156
+ function configShow(options) {
3157
+ if (options.json) {
3158
+ const output = buildConfigShowJson();
3159
+ console.log(JSON.stringify(output, null, 2));
3160
+ if ("error" in output)
3161
+ process.exit(1);
3162
+ return;
3163
+ }
2956
3164
  const paths = getConfigPaths(process.cwd());
2957
3165
  console.log(`Configuration locations:
2958
3166
  `);
@@ -2968,6 +3176,20 @@ Project configuration:`);
2968
3176
  User configuration:`);
2969
3177
  console.log(fs6.readFileSync(paths.userConfig, "utf-8"));
2970
3178
  }
3179
+ printResolvedSection();
3180
+ }
3181
+ function parseConfigShowArgs(rest) {
3182
+ let json = false;
3183
+ for (const arg of rest) {
3184
+ if (arg === "--json") {
3185
+ json = true;
3186
+ } else {
3187
+ console.error(`Unknown argument: ${arg}`);
3188
+ console.error(CONFIG_SHOW_USAGE);
3189
+ process.exit(1);
3190
+ }
3191
+ }
3192
+ return { json };
2971
3193
  }
2972
3194
  function configPath() {
2973
3195
  const paths = getConfigPaths(process.cwd());
@@ -3166,7 +3388,7 @@ function runLegacyCli(args) {
3166
3388
  switch (args[1]) {
3167
3389
  case "show":
3168
3390
  case undefined:
3169
- configShow();
3391
+ configShow(parseConfigShowArgs(args.slice(2)));
3170
3392
  break;
3171
3393
  case "path":
3172
3394
  configPath();