@fro.bot/systematic 3.15.1 → 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
@@ -23,14 +23,17 @@ import {
23
23
  discriminatedUnion,
24
24
  _enum,
25
25
  literal,
26
+ extractString,
27
+ toSourcedPiSubagentsOverlays,
28
+ resolveRouting,
26
29
  loadConfigWithSources,
30
+ collectRoutingTargets,
27
31
  getConfigPaths,
28
- extractString,
29
32
  findAgentsInDir,
30
33
  findCommandsInDir,
31
34
  findSkillsInDir,
32
35
  discoverSkills
33
- } from "./index-7cyxcwf5.js";
36
+ } from "./index-ae6mhwth.js";
34
37
  var __defProp = Object.defineProperty;
35
38
  var __returnValue = (v) => v;
36
39
  function __exportSetter(name, newValue) {
@@ -59,7 +62,15 @@ function buildAgentCatalog(agentsDir) {
59
62
  const entries = [];
60
63
  for (const info of infos) {
61
64
  const content = fs.readFileSync(info.file, "utf8");
62
- 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
+ };
63
74
  try {
64
75
  resolveToolAllowlist(entry.toolsSource);
65
76
  } catch (error) {
@@ -68,9 +79,9 @@ function buildAgentCatalog(agentsDir) {
68
79
  }
69
80
  const existing = byName.get(entry.name);
70
81
  if (existing) {
71
- existing.push(info.category ?? "(root)");
82
+ existing.push(category);
72
83
  } else {
73
- byName.set(entry.name, [info.category ?? "(root)"]);
84
+ byName.set(entry.name, [category]);
74
85
  }
75
86
  entries.push(entry);
76
87
  }
@@ -174,14 +185,19 @@ var CONFIG_AUTHORITY_FIELD_PATHS = [
174
185
  ];
175
186
  var CONFIG_PROTECTED_FIELD_PATHS = [
176
187
  "workflow_guard",
188
+ "profiles",
177
189
  "agents.*.model",
178
190
  "agents.*.permission",
179
191
  "agents.*.skills",
180
192
  "agents.*.variant",
193
+ "agents.*.opencode",
194
+ "agents.*.pi",
181
195
  "categories.*.model",
182
196
  "categories.*.permission",
183
197
  "categories.*.skills",
184
- "categories.*.variant"
198
+ "categories.*.variant",
199
+ "categories.*.opencode",
200
+ "categories.*.pi"
185
201
  ];
186
202
  var CONFIG_SOURCE_ERROR_CODES = [
187
203
  "parse-failed",
@@ -439,7 +455,14 @@ function configErrorCode(errorCode) {
439
455
  }
440
456
  function normalizeConfigObservation(config) {
441
457
  assertRecord(config, "config");
442
- assertAllowedKeys(config, ["authorities", "protectedFields", "sources"], "config");
458
+ assertAllowedKeys(config, [
459
+ "authorities",
460
+ "protectedFields",
461
+ "sources",
462
+ "activeProfile",
463
+ "profileSelectorSource",
464
+ "profileFallback"
465
+ ], "config");
443
466
  if (!Array.isArray(config.sources)) {
444
467
  throw new TypeError("config.sources must be an array");
445
468
  }
@@ -1496,21 +1519,19 @@ function lookupOverlay(map, category, agentKey) {
1496
1519
  }
1497
1520
  return map[agentKey];
1498
1521
  }
1499
- function resolveModel(config, category, agentKey) {
1500
- const agentOverlay = lookupOverlay(config.agents, category, agentKey);
1501
- if (agentOverlay && Object.hasOwn(agentOverlay, "model")) {
1502
- const value = agentOverlay.model;
1503
- return typeof value === "string" ? value : undefined;
1504
- }
1505
- const categoryOverlay = category ? config.categories?.[category] : undefined;
1506
- if (categoryOverlay && Object.hasOwn(categoryOverlay, "model")) {
1507
- const value = categoryOverlay.model;
1508
- return typeof value === "string" ? value : undefined;
1509
- }
1510
- 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
+ };
1511
1533
  }
1512
- var PI_SUBAGENTS_FRONTMATTER_FIELDS = [
1513
- "thinking",
1534
+ var OTHER_PI_SUBAGENTS_FRONTMATTER_FIELDS = [
1514
1535
  "max_turns",
1515
1536
  "tools",
1516
1537
  "skills"
@@ -1519,7 +1540,7 @@ function resolvePiSubagentsFields(config, category, agentKey) {
1519
1540
  const categoryOverlay = category ? config.pi_subagents?.categories?.[category] : undefined;
1520
1541
  const agentOverlay = lookupOverlay(config.pi_subagents?.agents, category, agentKey);
1521
1542
  const result = {};
1522
- for (const field of PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1543
+ for (const field of OTHER_PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1523
1544
  if (agentOverlay && Object.hasOwn(agentOverlay, field)) {
1524
1545
  result[field] = agentOverlay[field];
1525
1546
  } else if (categoryOverlay && Object.hasOwn(categoryOverlay, field)) {
@@ -1528,19 +1549,21 @@ function resolvePiSubagentsFields(config, category, agentKey) {
1528
1549
  }
1529
1550
  return result;
1530
1551
  }
1531
- function applyConfigToContent(content, config, category, agentKey) {
1552
+ function applyConfigToContent(content, config, overlays, piSubagentsOverlays, category, agentKey) {
1532
1553
  const { data, body } = parseFrontmatter(content);
1533
- const model = resolveModel(config, category, agentKey);
1534
- const piSubagentsFields = resolvePiSubagentsFields(config, category, agentKey);
1554
+ const { model, thinking } = resolveRoutedFields(overlays, piSubagentsOverlays, category, agentKey);
1555
+ const otherFields = resolvePiSubagentsFields(config, category, agentKey);
1535
1556
  const frontmatter = {};
1536
1557
  if (typeof data.description === "string") {
1537
1558
  frontmatter.description = data.description;
1538
1559
  }
1539
1560
  if (model !== undefined)
1540
1561
  frontmatter.model = model;
1541
- for (const field of PI_SUBAGENTS_FRONTMATTER_FIELDS) {
1542
- if (Object.hasOwn(piSubagentsFields, field)) {
1543
- 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];
1544
1567
  }
1545
1568
  }
1546
1569
  let frontmatterBlock;
@@ -1559,12 +1582,12 @@ ${lines}
1559
1582
 
1560
1583
  ${trimmedBody}`;
1561
1584
  }
1562
- function applyConfigToEntries(entries, config) {
1585
+ function applyConfigToEntries(entries, config, overlays, piSubagentsOverlays) {
1563
1586
  return entries.map((entry) => {
1564
1587
  if (entry.status === "excluded-critical" || !entry.content)
1565
1588
  return entry;
1566
1589
  const { category, agentKey } = parseSourceCategoryAndKey(entry.sourceRelPath);
1567
- const content = applyConfigToContent(entry.content, config, category, agentKey);
1590
+ const content = applyConfigToContent(entry.content, config, overlays, piSubagentsOverlays, category, agentKey);
1568
1591
  const hash = crypto2.createHash("sha256").update(content).digest("hex");
1569
1592
  return { ...entry, content, hash };
1570
1593
  });
@@ -1573,10 +1596,11 @@ function generateEntries(repoRoot, configOptions) {
1573
1596
  const entries = generateAll(repoRoot);
1574
1597
  if (!configOptions)
1575
1598
  return entries;
1576
- const config = loadConfigWithSources(configOptions.cwd, {
1599
+ const { config, overlays } = loadConfigWithSources(configOptions.cwd, {
1577
1600
  includeProject: configOptions.scope === "project"
1578
- }).config;
1579
- return applyConfigToEntries(entries, config);
1601
+ });
1602
+ const piSubagentsOverlays = toSourcedPiSubagentsOverlays(config.pi_subagents);
1603
+ return applyConfigToEntries(entries, config, overlays, piSubagentsOverlays);
1580
1604
  }
1581
1605
  function findPackageRoot() {
1582
1606
  const thisFile = fileURLToPath(import.meta.url);
@@ -2703,7 +2727,7 @@ Commands:
2703
2727
  Exit statuses: 0 valid artifact, 1 validation failure,
2704
2728
  2 operational failure, 3 legacy artifact with no schema_version
2705
2729
  config [subcommand] Configuration management
2706
- show Show configuration
2730
+ show [--json] Show configuration (--json for a machine-readable resolved view)
2707
2731
  path Print config file locations
2708
2732
  setup --harness opencode|pi Configure a harness to load Systematic (project-local only)
2709
2733
  pi-subagents <subcommand> Manage Systematic persona export for pi-subagents
@@ -2722,6 +2746,7 @@ Examples:
2722
2746
  systematic validate-review-artifact .context/systematic/ce-review/review-summary.json
2723
2747
  systematic list agents
2724
2748
  systematic config show
2749
+ systematic config show --json
2725
2750
  systematic setup --harness opencode
2726
2751
  systematic setup --harness pi
2727
2752
  systematic pi-subagents preview
@@ -2730,6 +2755,13 @@ Examples:
2730
2755
  systematic pi-subagents refresh
2731
2756
  systematic pi-subagents cleanup
2732
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
+ `;
2733
2765
  var PI_SUBAGENTS_USAGE = `Usage: systematic pi-subagents <subcommand> [--scope project|global]
2734
2766
 
2735
2767
  Subcommands:
@@ -3005,7 +3037,130 @@ function listItems(type) {
3005
3037
  console.log(` ${item.name}`);
3006
3038
  }
3007
3039
  }
3008
- 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
+ }
3009
3164
  const paths = getConfigPaths(process.cwd());
3010
3165
  console.log(`Configuration locations:
3011
3166
  `);
@@ -3021,6 +3176,20 @@ Project configuration:`);
3021
3176
  User configuration:`);
3022
3177
  console.log(fs6.readFileSync(paths.userConfig, "utf-8"));
3023
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 };
3024
3193
  }
3025
3194
  function configPath() {
3026
3195
  const paths = getConfigPaths(process.cwd());
@@ -3219,7 +3388,7 @@ function runLegacyCli(args) {
3219
3388
  switch (args[1]) {
3220
3389
  case "show":
3221
3390
  case undefined:
3222
- configShow();
3391
+ configShow(parseConfigShowArgs(args.slice(2)));
3223
3392
  break;
3224
3393
  case "path":
3225
3394
  configPath();