@fro.bot/systematic 2.9.2 → 2.11.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/README.md +48 -5
- package/dist/cli.js +1 -1
- package/dist/{index-d5ewqz8w.js → index-mfy9dbdx.js} +6 -1
- package/dist/index.js +99 -6
- package/dist/lib/agent-overlays.d.ts +24 -0
- package/dist/lib/config-handler.d.ts +2 -0
- package/package.json +1 -1
- package/skills/onboarding/scripts/inventory.mjs +5 -5
- package/skills/writing-systematic-skills/SKILL.md +1 -1
- package/skills/writing-systematic-skills/references/foundation-conventions.md +3 -1
package/README.md
CHANGED
|
@@ -326,11 +326,54 @@ Configuration is loaded from multiple locations and merged (later sources overri
|
|
|
326
326
|
| `bootstrap.enabled` | `boolean` | `true` | Inject the `using-systematic` guide into system prompts |
|
|
327
327
|
| `bootstrap.file` | `string` | — | Custom bootstrap file path (overrides default) |
|
|
328
328
|
|
|
329
|
-
Agent overlays support `model`, `variant`, `temperature`, `top_p`, `permission`, `mode`, `color`, `steps`, `hidden`, exact-agent-only `disable`, and managed `skills`. `color` accepts `#RGB`, `#RRGGBB`, or OpenCode named color tokens matching `[a-zA-Z][a-zA-Z0-9-]*`; whitespace/freeform numeric strings are rejected. `skills` uses bundled skill frontmatter names like `ce:review`; it is a shortcut that writes OpenCode `permission.skill` rules, not a native OpenCode agent field. Because `model`
|
|
330
|
-
|
|
331
|
-
Systematic separates config-source precedence from overlay precedence. Config files merge in this order: user config, project config, then `$OPENCODE_CONFIG_DIR/systematic.json` if set. Higher-priority `agents.<key>` and `categories.<id>` entries replace lower-priority entries wholesale, while unrelated keys survive. Project overlays are the exception for trust-sensitive fields: same-key project overlays preserve user-level `model`, `permission`, and `skills` fields instead of erasing them. After the effective config is built,
|
|
332
|
-
|
|
333
|
-
|
|
329
|
+
Agent overlays support `model`, `variant`, `temperature`, `top_p`, `permission`, `mode`, `color`, `steps`, `hidden`, exact-agent-only `disable`, and managed `skills`. `color` accepts `#RGB`, `#RRGGBB`, or OpenCode named color tokens matching `[a-zA-Z][a-zA-Z0-9-]*`; whitespace/freeform numeric strings are rejected. `skills` uses bundled skill frontmatter names like `ce:review`; it is a shortcut that writes OpenCode `permission.skill` rules, not a native OpenCode agent field. Because `model` and `variant` control provider routing/cost/privacy and `permission`/`skills` control tool access, those fields are only accepted from user config or `$OPENCODE_CONFIG_DIR/systematic.json`. Project config may tune non-sensitive presentation and runtime fields such as `temperature`, `top_p`, `mode`, `color`, `steps`, `hidden`, or exact-agent `disable`, but it cannot choose model/provider routing, tune `variant`, or loosen permission/capability policy.
|
|
330
|
+
|
|
331
|
+
Systematic separates config-source precedence from overlay precedence. Config files merge in this order: user config, project config, then `$OPENCODE_CONFIG_DIR/systematic.json` if set. Higher-priority `agents.<key>` and `categories.<id>` entries replace lower-priority entries wholesale, while unrelated keys survive. Project overlays are the exception for trust-sensitive fields: same-key project overlays preserve user-level `model`, `variant`, `permission`, and `skills` fields instead of erasing them. After the effective config is built, Systematic applies agent overlay precedence for bundled agents:
|
|
332
|
+
|
|
333
|
+
1. Exact `agents.<key>` overlay (high-trust `model` wins)
|
|
334
|
+
2. `categories.<category-id>` overlay (high-trust `model` wins)
|
|
335
|
+
3. Source category model default (built-in, code-owned)
|
|
336
|
+
4. Bundled markdown/frontmatter defaults
|
|
337
|
+
5. OpenCode inherited defaults
|
|
338
|
+
|
|
339
|
+
Source category model defaults are primary model choices only — they are not fallback chains. Systematic does not support `fallback_models`, inherited retry semantics, runtime fallback behavior, or fallback to the parent model when a source model is unavailable. Explicit and source model IDs are structurally validated and may still fail at OpenCode runtime if the provider or model is unavailable.
|
|
340
|
+
|
|
341
|
+
Source category model defaults are now ordered preference arrays per category rather than single strings. At plugin load, Systematic reads OpenCode's authentication state from `auth.json` and selects the first array entry whose provider is authenticated. For example, the `review` category defaults to `['anthropic/claude-opus-4.7', 'openai/gpt-5.5']` — a user authenticated only to OpenAI receives `openai/gpt-5.5` (first match), while a user authenticated to Anthropic (or both) receives the more preferred `anthropic/claude-opus-4.7`. If no array entry's provider is authenticated, the first entry is used as the default. The arrays are an ordered preference list, not a runtime fallback chain — `fallback_models` is still not supported.
|
|
342
|
+
|
|
343
|
+
If you want to restore OpenCode parent-model inheritance for a bundled agent or category (opting out of the source default), set `"model": null` in high-trust user or `$OPENCODE_CONFIG_DIR/systematic.json` config. Project config cannot use `model: null` — project config cannot set, erase, or shadow `model` at any value.
|
|
344
|
+
|
|
345
|
+
The source defaults are:
|
|
346
|
+
|
|
347
|
+
| Category | Default `model` | Rationale |
|
|
348
|
+
|----------|-----------------|-----------|
|
|
349
|
+
| `design` | `openai/gpt-5.5` | High-judgment UX/product/design work benefits from a strong general reasoning model. |
|
|
350
|
+
| `docs` | `openai/gpt-5.4-mini` | Documentation and summarization should start cheaper/faster. |
|
|
351
|
+
| `document-review` | `anthropic/claude-opus-4.7` | Requirements and plan critique benefit from strongest nuanced reasoning. |
|
|
352
|
+
| `research` | `openai/gpt-5.5` | Tool-heavy synthesis and source evaluation benefit from a strong general reasoning model. |
|
|
353
|
+
| `review` | `anthropic/claude-opus-4.7` | Code/security/adversarial review benefits from strongest reasoning. |
|
|
354
|
+
| `workflow` | `openai/gpt-5.4-mini` | Orchestration and bounded implementation should default cheaper/faster. |
|
|
355
|
+
|
|
356
|
+
These defaults are owned by Systematic code and emitted for bundled agents in each category when no stronger high-trust exact or category `model` override exists. Uncategorized bundled agents receive no source default and continue inheriting the parent OpenCode model. Native OpenCode agents with the same emitted key are full replacements and receive no Systematic source model default.
|
|
357
|
+
|
|
358
|
+
Bundled agent markdown still intentionally omits `model` — the field belongs in source code defaults, not portable markdown files. Authors must not add `model:` frontmatter to bundled agent files.
|
|
359
|
+
|
|
360
|
+
Systematic emits a source model as the default; you can override it per-agent or per-category in user or `$OPENCODE_CONFIG_DIR/systematic.json` config. Project config cannot set, erase, or shadow `model` policy.
|
|
361
|
+
|
|
362
|
+
> **Migration: Restoring parent-model inheritance.** If you previously relied on bundled agents inheriting the parent OpenCode model (no source defaults), set `"model": null` in your high-trust config to opt out of the source default per agent or per category. For example:
|
|
363
|
+
>
|
|
364
|
+
> ```jsonc
|
|
365
|
+
> // ~/.config/opencode/systematic.json or $OPENCODE_CONFIG_DIR/systematic.json
|
|
366
|
+
> {
|
|
367
|
+
> "categories": {
|
|
368
|
+
> "review": { "model": null } // All review agents inherit parent model
|
|
369
|
+
> },
|
|
370
|
+
> "agents": {
|
|
371
|
+
> "security-sentinel": { "model": null } // Single agent inherits parent model
|
|
372
|
+
> }
|
|
373
|
+
> }
|
|
374
|
+
> ```
|
|
375
|
+
>
|
|
376
|
+
> This only works from high-trust config (user or `$OPENCODE_CONFIG_DIR/systematic.json`). Project `.opencode/systematic.json` cannot set `model: null` or any `model` value.
|
|
334
377
|
|
|
335
378
|
Native OpenCode agents with the same emitted key are full replacements. An exact Systematic overlay for that key conflicts, while category overlays skip native replacements and continue applying to other bundled agents. Use one canonical agent key form across config sources (`security-sentinel` or `review/security-sentinel`) because alias collisions fail duplicate-target validation.
|
|
336
379
|
|
package/dist/cli.js
CHANGED
|
@@ -856,7 +856,12 @@ var DEFAULT_CONFIG = {
|
|
|
856
856
|
agents: {},
|
|
857
857
|
categories: {}
|
|
858
858
|
};
|
|
859
|
-
var SECURITY_OVERLAY_FIELDS = new Set([
|
|
859
|
+
var SECURITY_OVERLAY_FIELDS = new Set([
|
|
860
|
+
"model",
|
|
861
|
+
"variant",
|
|
862
|
+
"permission",
|
|
863
|
+
"skills"
|
|
864
|
+
]);
|
|
860
865
|
function isErrorWithCode(error) {
|
|
861
866
|
return error instanceof Error && "code" in error;
|
|
862
867
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
loadConfig,
|
|
13
13
|
loadConfigWithSources,
|
|
14
14
|
parseFrontmatter
|
|
15
|
-
} from "./index-
|
|
15
|
+
} from "./index-mfy9dbdx.js";
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
18
|
import fs4 from "fs";
|
|
@@ -78,7 +78,16 @@ ${toolMapping}
|
|
|
78
78
|
|
|
79
79
|
// src/lib/agent-overlays.ts
|
|
80
80
|
import fs2 from "fs";
|
|
81
|
+
import os2 from "os";
|
|
81
82
|
import path2 from "path";
|
|
83
|
+
var SOURCE_CATEGORY_MODEL_DEFAULTS = {
|
|
84
|
+
design: ["openai/gpt-5.5", "anthropic/claude-opus-4.7"],
|
|
85
|
+
docs: ["openai/gpt-5.4-mini", "anthropic/claude-haiku-4-5"],
|
|
86
|
+
"document-review": ["anthropic/claude-opus-4.7", "openai/gpt-5.5"],
|
|
87
|
+
research: ["openai/gpt-5.5", "anthropic/claude-opus-4.7"],
|
|
88
|
+
review: ["anthropic/claude-opus-4.7", "openai/gpt-5.5"],
|
|
89
|
+
workflow: ["openai/gpt-5.4-mini", "anthropic/claude-haiku-4-5"]
|
|
90
|
+
};
|
|
82
91
|
var ALLOWED_OVERLAY_FIELDS = new Set([
|
|
83
92
|
"model",
|
|
84
93
|
"variant",
|
|
@@ -158,6 +167,72 @@ function inferBuiltInTemperature(name, description) {
|
|
|
158
167
|
}
|
|
159
168
|
return 0.3;
|
|
160
169
|
}
|
|
170
|
+
function getAuthenticatedProviders(rootDirOverride) {
|
|
171
|
+
const xdgDataHome = process.env.XDG_DATA_HOME?.trim();
|
|
172
|
+
const rootDir = rootDirOverride || (xdgDataHome && path2.isAbsolute(xdgDataHome) ? xdgDataHome : path2.join(os2.homedir(), ".local/share"));
|
|
173
|
+
const authPath = path2.join(rootDir, "opencode", "auth.json");
|
|
174
|
+
let raw;
|
|
175
|
+
try {
|
|
176
|
+
raw = fs2.readFileSync(authPath, "utf8");
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (isSystemError(err) && err.code === "ENOENT") {
|
|
179
|
+
return new Set;
|
|
180
|
+
}
|
|
181
|
+
console.warn(`[systematic] auth.json unreadable at ${authPath}; ignoring`);
|
|
182
|
+
return new Set;
|
|
183
|
+
}
|
|
184
|
+
let parsed;
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(raw);
|
|
187
|
+
} catch {
|
|
188
|
+
console.warn(`[systematic] auth.json malformed at ${authPath}; ignoring`);
|
|
189
|
+
return new Set;
|
|
190
|
+
}
|
|
191
|
+
if (!isRecord(parsed)) {
|
|
192
|
+
console.warn(`[systematic] auth.json malformed at ${authPath}; ignoring`);
|
|
193
|
+
return new Set;
|
|
194
|
+
}
|
|
195
|
+
return new Set(Object.keys(parsed));
|
|
196
|
+
}
|
|
197
|
+
function getSourceCategoryModel(category, authedProviders) {
|
|
198
|
+
if (!category)
|
|
199
|
+
return;
|
|
200
|
+
const candidates = SOURCE_CATEGORY_MODEL_DEFAULTS[category];
|
|
201
|
+
if (!candidates || candidates.length === 0)
|
|
202
|
+
return;
|
|
203
|
+
if (!authedProviders || authedProviders.size === 0)
|
|
204
|
+
return candidates[0];
|
|
205
|
+
for (const entry of candidates) {
|
|
206
|
+
const slashIndex = entry.indexOf("/");
|
|
207
|
+
if (slashIndex <= 0)
|
|
208
|
+
continue;
|
|
209
|
+
const providerId = entry.slice(0, slashIndex);
|
|
210
|
+
if (authedProviders.has(providerId)) {
|
|
211
|
+
return entry;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return candidates[0];
|
|
215
|
+
}
|
|
216
|
+
function assertSourceCategoryModelCoverage(categories) {
|
|
217
|
+
validateSourceCategoryModelDefaults();
|
|
218
|
+
const missingCategories = categories.filter((category) => !Object.hasOwn(SOURCE_CATEGORY_MODEL_DEFAULTS, category));
|
|
219
|
+
if (missingCategories.length > 0) {
|
|
220
|
+
throw new Error(`Source category model defaults missing intentional coverage for: ${missingCategories.join(", ")}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function validateSourceCategoryModelDefaults(defaults = SOURCE_CATEGORY_MODEL_DEFAULTS) {
|
|
224
|
+
for (const [category, value] of Object.entries(defaults)) {
|
|
225
|
+
if (!Array.isArray(value)) {
|
|
226
|
+
throw new Error(`Source category model defaults: ${category} must be a non-empty array of provider/model strings`);
|
|
227
|
+
}
|
|
228
|
+
if (value.length === 0) {
|
|
229
|
+
throw new Error(`Source category model defaults: ${category} must be a non-empty array of provider/model strings`);
|
|
230
|
+
}
|
|
231
|
+
for (const [index, model] of value.entries()) {
|
|
232
|
+
validateModel("source category model defaults", `source category model defaults.${category}[${index}]`, model);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
161
236
|
function validateExactAgentOverlays(inventory, overlays, nativeAgents, enabledSkills) {
|
|
162
237
|
const result = [];
|
|
163
238
|
const seenTargets = new Map;
|
|
@@ -227,6 +302,8 @@ function hasPermissionSkill(permission) {
|
|
|
227
302
|
function validateOverlayFieldValue(sourcePath, keyPath, field, value, enabledSkills) {
|
|
228
303
|
switch (field) {
|
|
229
304
|
case "model":
|
|
305
|
+
if (value === null)
|
|
306
|
+
return;
|
|
230
307
|
validateModel(sourcePath, keyPath, value);
|
|
231
308
|
return;
|
|
232
309
|
case "variant":
|
|
@@ -371,6 +448,9 @@ function validAgentKeys(inventory) {
|
|
|
371
448
|
function throwConfigError(sourcePath, keyPath, message) {
|
|
372
449
|
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath} ${message}`);
|
|
373
450
|
}
|
|
451
|
+
function isSystemError(err) {
|
|
452
|
+
return typeof err === "object" && err !== null && "code" in err && typeof err.code === "string";
|
|
453
|
+
}
|
|
374
454
|
|
|
375
455
|
// src/lib/skill-loader.ts
|
|
376
456
|
import path3 from "path";
|
|
@@ -540,7 +620,7 @@ function loadSkillAsCommand(loaded) {
|
|
|
540
620
|
config.subtask = loaded.subtask;
|
|
541
621
|
return config;
|
|
542
622
|
}
|
|
543
|
-
function collectAgents(dir, disabledAgents, nativeAgents, overlays) {
|
|
623
|
+
function collectAgents(dir, disabledAgents, nativeAgents, overlays, authedProviders) {
|
|
544
624
|
const agents = {};
|
|
545
625
|
const agentList = findAgentsInDir(dir);
|
|
546
626
|
const disabledSet = new Set(disabledAgents);
|
|
@@ -555,12 +635,12 @@ function collectAgents(dir, disabledAgents, nativeAgents, overlays) {
|
|
|
555
635
|
continue;
|
|
556
636
|
const config = loadAgentAsConfig(agentInfo);
|
|
557
637
|
if (config) {
|
|
558
|
-
agents[agentInfo.name] = applyAgentOverlays(config, agentInfo, overlays);
|
|
638
|
+
agents[agentInfo.name] = applyAgentOverlays(config, agentInfo, overlays, authedProviders);
|
|
559
639
|
}
|
|
560
640
|
}
|
|
561
641
|
return agents;
|
|
562
642
|
}
|
|
563
|
-
function applyAgentOverlays(config, agentInfo, overlays) {
|
|
643
|
+
function applyAgentOverlays(config, agentInfo, overlays, authedProviders) {
|
|
564
644
|
const id = agentInfo.category ? `${agentInfo.category}/${agentInfo.name}` : agentInfo.name;
|
|
565
645
|
const categoryOverlay = agentInfo.category ? overlays.categoriesByKey.get(agentInfo.category) : undefined;
|
|
566
646
|
const exactOverlay = overlays.agentsByTargetId.get(id);
|
|
@@ -571,6 +651,12 @@ function applyAgentOverlays(config, agentInfo, overlays) {
|
|
|
571
651
|
addPermissionRules(permissionRules, config.permission);
|
|
572
652
|
}
|
|
573
653
|
result.temperature = inferBuiltInTemperature(agentInfo.name, result.description);
|
|
654
|
+
if (agentInfo.category) {
|
|
655
|
+
const sourceModel = getSourceCategoryModel(agentInfo.category, authedProviders);
|
|
656
|
+
if (sourceModel) {
|
|
657
|
+
result.model = sourceModel;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
574
660
|
if (categoryOverlay) {
|
|
575
661
|
applyOverlayObject(result, categoryOverlay.value, permissionRules);
|
|
576
662
|
}
|
|
@@ -603,7 +689,11 @@ var OVERLAY_ASSIGN_FIELDS = [
|
|
|
603
689
|
function applyOverlayObject(target, overlay, permissionRules) {
|
|
604
690
|
for (const field of OVERLAY_ASSIGN_FIELDS) {
|
|
605
691
|
if (Object.hasOwn(overlay, field)) {
|
|
606
|
-
|
|
692
|
+
if (field === "model" && overlay[field] === null) {
|
|
693
|
+
delete target[field];
|
|
694
|
+
} else {
|
|
695
|
+
target[field] = overlay[field];
|
|
696
|
+
}
|
|
607
697
|
}
|
|
608
698
|
}
|
|
609
699
|
if (isRecord(overlay.permission)) {
|
|
@@ -692,6 +782,7 @@ function collectEnabledSkillNames(dir, disabledSkills) {
|
|
|
692
782
|
}
|
|
693
783
|
function createConfigHandler(deps) {
|
|
694
784
|
const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
|
|
785
|
+
const readAuthProviders = deps.getAuthenticatedProviders ?? getAuthenticatedProviders;
|
|
695
786
|
return async (config) => {
|
|
696
787
|
const { config: systematicConfig, overlays } = loadConfigWithSources(directory);
|
|
697
788
|
const existingAgents = { ...config.agent ?? {} };
|
|
@@ -699,6 +790,7 @@ function createConfigHandler(deps) {
|
|
|
699
790
|
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
700
791
|
const enabledSkillNames = collectEnabledSkillNames(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
701
792
|
const inventory = buildBundledAgentInventory(bundledAgentsDir, systematicConfig.disabled_agents);
|
|
793
|
+
assertSourceCategoryModelCoverage(inventory.categories);
|
|
702
794
|
const validatedOverlays = validateAgentOverlays({
|
|
703
795
|
inventory,
|
|
704
796
|
overlays,
|
|
@@ -706,7 +798,8 @@ function createConfigHandler(deps) {
|
|
|
706
798
|
enabledSkills: enabledSkillNames
|
|
707
799
|
});
|
|
708
800
|
const resolvedOverlays = resolveAgentOverlaySet(validatedOverlays);
|
|
709
|
-
const
|
|
801
|
+
const authedProviders = readAuthProviders();
|
|
802
|
+
const bundledAgents = collectAgents(bundledAgentsDir, systematicConfig.disabled_agents, existingAgents, resolvedOverlays, authedProviders);
|
|
710
803
|
const bundledCommands = collectCommands(bundledCommandsDir, systematicConfig.disabled_commands);
|
|
711
804
|
config.agent = {
|
|
712
805
|
...bundledAgents,
|
|
@@ -42,3 +42,27 @@ export declare function buildBundledAgentInventory(agentsDir: string, disabledAg
|
|
|
42
42
|
export declare function validateAgentOverlays({ inventory, overlays, nativeAgents, enabledSkills, }: ValidateAgentOverlaysOptions): ValidatedAgentOverlays;
|
|
43
43
|
export declare function resolveAgentOverlaySet(overlays: ValidatedAgentOverlays): ResolvedAgentOverlaySet;
|
|
44
44
|
export declare function inferBuiltInTemperature(name: string, description?: string): number;
|
|
45
|
+
/**
|
|
46
|
+
* Read which providers are authenticated from OpenCode's auth.json.
|
|
47
|
+
*
|
|
48
|
+
* Reads only top-level keys (provider IDs). Nested values are NEVER
|
|
49
|
+
* inspected, logged, persisted, or transmitted. This is a hard contract:
|
|
50
|
+
* the auth file holds API keys and OAuth tokens, and Systematic must
|
|
51
|
+
* never expose them via stderr, telemetry, or any other channel.
|
|
52
|
+
*
|
|
53
|
+
* Intended for one invocation per plugin config(cfg) cycle. Repeated
|
|
54
|
+
* calls trigger repeated file reads and, on malformed input, repeated
|
|
55
|
+
* stderr diagnostics.
|
|
56
|
+
*
|
|
57
|
+
* @param rootDirOverride - Optional path override for tests. When
|
|
58
|
+
* non-empty, the auth file is resolved as
|
|
59
|
+
* `path.join(rootDirOverride, 'opencode', 'auth.json')`. When
|
|
60
|
+
* omitted, resolution follows XDG_DATA_HOME -> ~/.local/share
|
|
61
|
+
* convention.
|
|
62
|
+
* @returns A readonly set of authenticated provider IDs (empty set on
|
|
63
|
+
* any failure).
|
|
64
|
+
*/
|
|
65
|
+
export declare function getAuthenticatedProviders(rootDirOverride?: string): ReadonlySet<string>;
|
|
66
|
+
export declare function getSourceCategoryModel(category: string | undefined, authedProviders?: ReadonlySet<string>): string | undefined;
|
|
67
|
+
export declare function assertSourceCategoryModelCoverage(categories: string[]): void;
|
|
68
|
+
export declare function validateSourceCategoryModelDefaults(defaults?: Record<string, unknown>): void;
|
|
@@ -4,6 +4,8 @@ export interface ConfigHandlerDeps {
|
|
|
4
4
|
bundledSkillsDir: string;
|
|
5
5
|
bundledAgentsDir: string;
|
|
6
6
|
bundledCommandsDir: string;
|
|
7
|
+
/** Override for authenticated provider reader; for testing. */
|
|
8
|
+
getAuthenticatedProviders?: (rootDirOverride?: string) => ReadonlySet<string>;
|
|
7
9
|
}
|
|
8
10
|
export declare function toTitleCase(name: string): string;
|
|
9
11
|
export declare function formatAgentDescription(name: string, description: string | undefined): string;
|
package/package.json
CHANGED
|
@@ -339,7 +339,7 @@ async function detectLanguagesAndFrameworks() {
|
|
|
339
339
|
if (allDeps[dep]) {
|
|
340
340
|
// Check exclusion rules before adding
|
|
341
341
|
const exclusions = NODE_FRAMEWORK_EXCLUSIONS[fw]
|
|
342
|
-
if (exclusions
|
|
342
|
+
if (exclusions?.some((ex) => allDeps[ex])) continue
|
|
343
343
|
|
|
344
344
|
const ver = allDeps[dep].replace(/[\^~>=<]/g, '').split(' ')[0]
|
|
345
345
|
frameworks.push(ver ? `${fw} ${ver}` : fw)
|
|
@@ -821,7 +821,7 @@ async function findTestInfra() {
|
|
|
821
821
|
'src/__tests__',
|
|
822
822
|
]
|
|
823
823
|
for (const dir of testDirs) {
|
|
824
|
-
if (await exists(join(root, dir))) dirs.push(dir
|
|
824
|
+
if (await exists(join(root, dir))) dirs.push(`${dir}/`)
|
|
825
825
|
}
|
|
826
826
|
|
|
827
827
|
// Test config files
|
|
@@ -1042,13 +1042,13 @@ async function main() {
|
|
|
1042
1042
|
infrastructure,
|
|
1043
1043
|
}
|
|
1044
1044
|
|
|
1045
|
-
process.stdout.write(JSON.stringify(inventory)
|
|
1045
|
+
process.stdout.write(`${JSON.stringify(inventory)}\n`)
|
|
1046
1046
|
}
|
|
1047
1047
|
|
|
1048
1048
|
main().catch((err) => {
|
|
1049
1049
|
// Always exit 0 with valid JSON, even on error
|
|
1050
1050
|
process.stdout.write(
|
|
1051
|
-
JSON.stringify({
|
|
1051
|
+
`${JSON.stringify({
|
|
1052
1052
|
error: err.message,
|
|
1053
1053
|
name: basename(root),
|
|
1054
1054
|
languages: [],
|
|
@@ -1062,6 +1062,6 @@ main().catch((err) => {
|
|
|
1062
1062
|
docs: [],
|
|
1063
1063
|
testInfra: { dirs: [], config: [] },
|
|
1064
1064
|
infrastructure: { envFiles: [], configFiles: [], services: [] },
|
|
1065
|
-
})
|
|
1065
|
+
})}\n`,
|
|
1066
1066
|
)
|
|
1067
1067
|
})
|
|
@@ -86,7 +86,7 @@ description: ...
|
|
|
86
86
|
---
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Per [OpenCode's agent docs](https://opencode.ai/docs/agents/), subagents with no `model` inherit the model of the primary agent that invoked them — which is the desired portable behavior. Do **not** declare `model: inherit`: that literal value is undocumented and produces `ProviderModelNotFoundError` on OpenCode older than ~v1.13.x (pre [sst/opencode#17888](https://github.com/sst/opencode/pull/17888)). Hardcoded provider model IDs (`anthropic/...`, `openai/...`, etc.) are also banned because they break users on other providers.
|
|
89
|
+
Per [OpenCode's agent docs](https://opencode.ai/docs/agents/), subagents with no `model` inherit the model of the primary agent that invoked them — which is the desired portable behavior. Do **not** declare `model: inherit`: that literal value is undocumented and produces `ProviderModelNotFoundError` on OpenCode older than ~v1.13.x (pre [sst/opencode#17888](https://github.com/sst/opencode/pull/17888)). Hardcoded provider model IDs (`anthropic/...`, `openai/...`, etc.) are also banned from **bundled agent markdown/frontmatter** because they break users on other providers. Source-owned category model defaults in TypeScript code are a separate mechanism — they are audited, centrally maintained, and do not violate this markdown rule.
|
|
90
90
|
|
|
91
91
|
For agent or API attribution, `ai:systematic` is the machine ID used by Systematic-owned operations, such as Proof's `by` field and `X-Agent-Id` header. It is not a skill cross-reference convention.
|
|
92
92
|
|
|
@@ -120,7 +120,9 @@ Per [OpenCode's agent docs](https://opencode.ai/docs/agents/): **"If you don't s
|
|
|
120
120
|
|
|
121
121
|
Do **not** declare `model: inherit`. That literal value is undocumented and was treated as a real provider/model string by `Provider.parseModel()` until [sst/opencode#17888](https://github.com/sst/opencode/pull/17888) landed in mid-March 2026 — producing `ProviderModelNotFoundError` on every subagent invocation for anyone on an older OpenCode. Omitting the field works on every OpenCode version and is what the docs canonically describe.
|
|
122
122
|
|
|
123
|
-
Hardcoded provider IDs (`anthropic/claude-...`, `openai/gpt-...`, etc.) are also banned because they make an agent unusable for users on other providers. If a future agent truly depends on a specific provider, document the constraint in the plan and get explicit review before adding the hardcoded model.
|
|
123
|
+
Hardcoded provider IDs (`anthropic/claude-...`, `openai/gpt-...`, etc.) are also banned from **bundled agent markdown/frontmatter** because they make an agent unusable for users on other providers. This ban does not apply to source-owned category model defaults in TypeScript code, which are centrally maintained, structurally validated, and emitted at the built-in/default layer during config handling. If a future agent truly depends on a specific provider in its markdown, document the constraint in the plan and get explicit review before adding the hardcoded model.
|
|
124
|
+
|
|
125
|
+
Systematic provides source-owned category model defaults in TypeScript code for all six bundled agent categories (`design`, `docs`, `document-review`, `research`, `review`, `workflow`). These code-level defaults are emitted during config handling and do not change the markdown contract: bundled agent files must still omit `model` frontmatter. The content-integrity gate continues to enforce the markdown-level ban independently of what source code defaults provide.
|
|
124
126
|
|
|
125
127
|
### Machine ID
|
|
126
128
|
|