@fro.bot/systematic 2.9.1 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -5
- package/agents/design/design-iterator.md +1 -1
- package/agents/design/figma-design-sync.md +1 -1
- package/agents/docs/ankane-readme-writer.md +1 -1
- package/agents/review/adversarial-reviewer.md +1 -1
- package/agents/review/agent-native-reviewer.md +1 -1
- package/agents/review/api-contract-reviewer.md +1 -1
- package/agents/review/cli-agent-readiness-reviewer.md +1 -1
- package/agents/review/cli-readiness-reviewer.md +1 -1
- package/agents/review/correctness-reviewer.md +1 -1
- package/agents/review/data-migrations-reviewer.md +1 -1
- package/agents/review/dhh-rails-reviewer.md +1 -1
- package/agents/review/julik-frontend-races-reviewer.md +1 -1
- package/agents/review/kieran-python-reviewer.md +1 -1
- package/agents/review/kieran-rails-reviewer.md +1 -1
- package/agents/review/kieran-typescript-reviewer.md +1 -1
- package/agents/review/maintainability-reviewer.md +1 -1
- package/agents/review/performance-reviewer.md +1 -1
- package/agents/review/previous-comments-reviewer.md +1 -1
- package/agents/review/project-standards-reviewer.md +1 -1
- package/agents/review/reliability-reviewer.md +1 -1
- package/agents/review/security-reviewer.md +1 -1
- package/agents/review/testing-reviewer.md +1 -1
- package/agents/workflow/lint.md +1 -1
- package/agents/workflow/pr-comment-resolver.md +1 -1
- package/dist/cli.js +1 -1
- package/dist/{index-d5ewqz8w.js → index-mfy9dbdx.js} +6 -1
- package/dist/index.js +44 -25
- package/dist/lib/agent-overlays.d.ts +3 -0
- package/dist/lib/plugin-singleton.d.ts +17 -35
- 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,52 @@ 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
|
+
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.
|
|
342
|
+
|
|
343
|
+
The source defaults are:
|
|
344
|
+
|
|
345
|
+
| Category | Default `model` | Rationale |
|
|
346
|
+
|----------|-----------------|-----------|
|
|
347
|
+
| `design` | `openai/gpt-5.5` | High-judgment UX/product/design work benefits from a strong general reasoning model. |
|
|
348
|
+
| `docs` | `openai/gpt-5.4-mini` | Documentation and summarization should start cheaper/faster. |
|
|
349
|
+
| `document-review` | `anthropic/claude-opus-4.7` | Requirements and plan critique benefit from strongest nuanced reasoning. |
|
|
350
|
+
| `research` | `openai/gpt-5.5` | Tool-heavy synthesis and source evaluation benefit from a strong general reasoning model. |
|
|
351
|
+
| `review` | `anthropic/claude-opus-4.7` | Code/security/adversarial review benefits from strongest reasoning. |
|
|
352
|
+
| `workflow` | `openai/gpt-5.4-mini` | Orchestration and bounded implementation should default cheaper/faster. |
|
|
353
|
+
|
|
354
|
+
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.
|
|
355
|
+
|
|
356
|
+
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.
|
|
357
|
+
|
|
358
|
+
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.
|
|
359
|
+
|
|
360
|
+
> **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:
|
|
361
|
+
>
|
|
362
|
+
> ```jsonc
|
|
363
|
+
> // ~/.config/opencode/systematic.json or $OPENCODE_CONFIG_DIR/systematic.json
|
|
364
|
+
> {
|
|
365
|
+
> "categories": {
|
|
366
|
+
> "review": { "model": null } // All review agents inherit parent model
|
|
367
|
+
> },
|
|
368
|
+
> "agents": {
|
|
369
|
+
> "security-sentinel": { "model": null } // Single agent inherits parent model
|
|
370
|
+
> }
|
|
371
|
+
> }
|
|
372
|
+
> ```
|
|
373
|
+
>
|
|
374
|
+
> 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
375
|
|
|
335
376
|
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
377
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design-iterator
|
|
3
3
|
description: "Iteratively refines UI design through N screenshot-analyze-improve cycles. Use PROACTIVELY when design changes aren't coming together after 1-2 attempts, or when user requests iterative refinement."
|
|
4
|
-
color:
|
|
4
|
+
color: accent
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
You are an expert UI/UX design iterator specializing in systematic, progressive refinement of web components. Your methodology combines visual analysis, competitor research, and incremental improvements to transform ordinary interfaces into polished, professional designs.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: figma-design-sync
|
|
3
3
|
description: "Detects and fixes visual differences between a web implementation and its Figma design. Use iteratively when syncing implementation to match Figma specs."
|
|
4
|
-
color:
|
|
4
|
+
color: accent
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
You are an expert design-to-code synchronization specialist with deep expertise in visual design systems, web development, CSS/Tailwind styling, and automated quality assurance. Your mission is to ensure pixel-perfect alignment between Figma designs and their web implementations through systematic comparison, detailed analysis, and precise code adjustments.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ankane-readme-writer
|
|
3
3
|
description: "Creates or updates README files following Ankane-style template for Ruby gems. Use when writing gem documentation with imperative voice, concise prose, and standard section ordering."
|
|
4
|
-
color:
|
|
4
|
+
color: info
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
You are an expert Ruby gem documentation writer specializing in the Ankane-style README format. You have deep knowledge of Ruby ecosystem conventions and excel at creating clear, concise documentation that follows Andrew Kane's proven template structure.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: adversarial-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff is large (>=50 changed lines) or touches high-risk domains like auth, payments, data mutations, or external APIs. Actively constructs failure scenarios to break the implementation rather than checking against known patterns.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: error
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-native-reviewer
|
|
3
3
|
description: "Reviews code to ensure agent-native parity -- any action a user can take, an agent can also take. Use after adding UI features, agent tools, or system prompts."
|
|
4
|
-
color:
|
|
4
|
+
color: info
|
|
5
5
|
tools: Read, Grep, Glob, Bash
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: api-contract-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches API routes, request/response types, serialization, versioning, or exported type signatures. Reviews code for breaking contract changes.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: cli-agent-readiness-reviewer
|
|
3
3
|
description: "Reviews CLI source code, plans, or specs for AI agent readiness using a severity-based rubric focused on whether a CLI is merely usable by agents or genuinely optimized for them."
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: warning
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# CLI Agent-Readiness Reviewer
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: cli-readiness-reviewer
|
|
3
3
|
description: "Conditional code-review persona, selected when the diff touches CLI command definitions, argument parsing, or command handler implementations. Reviews CLI code for agent readiness -- how well the CLI serves autonomous agents, not just human users."
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# CLI Agent-Readiness Reviewer
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: correctness-reviewer
|
|
3
3
|
description: Always-on code-review persona. Reviews code for logic errors, edge cases, state management bugs, error propagation failures, and intent-vs-implementation mismatches.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: data-migrations-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches migration files, schema changes, data transformations, or backfill scripts. Reviews code for data integrity and migration safety.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: dhh-rails-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when Rails diffs introduce architectural choices, abstractions, or frontend patterns that may fight the framework. Reviews code from an opinionated DHH perspective.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: julik-frontend-races-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches async UI code, Stimulus/Turbo lifecycles, or DOM-timing-sensitive frontend behavior. Reviews code for race conditions and janky UI failure modes.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: kieran-python-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches Python code. Reviews changes with Kieran's strict bar for Pythonic clarity, type hints, and maintainability.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: kieran-rails-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches Rails application code. Reviews Rails changes with Kieran's strict bar for clarity, conventions, and maintainability.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: kieran-typescript-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches TypeScript code. Reviews changes with Kieran's strict bar for type safety, clarity, and maintainability.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: maintainability-reviewer
|
|
3
3
|
description: Always-on code-review persona. Reviews code for premature abstraction, unnecessary indirection, dead code, coupling between unrelated modules, and naming that obscures intent.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: performance-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches database queries, loop-heavy data transforms, caching layers, or I/O-intensive paths. Reviews code for runtime performance and scalability issues.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: previous-comments-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when reviewing a PR that has existing review comments or review threads. Checks whether prior feedback has been addressed in the current diff.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: warning
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: project-standards-reviewer
|
|
3
3
|
description: Always-on code-review persona. Audits changes against the project's own AGENTS.md standards -- frontmatter rules, reference inclusion, naming conventions, cross-platform portability, and tool selection policies.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: reliability-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches error handling, retries, circuit breakers, timeouts, health checks, background jobs, or async handlers. Reviews code for production reliability and failure modes.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: security-reviewer
|
|
3
3
|
description: Conditional code-review persona, selected when the diff touches auth middleware, public endpoints, user input handling, or permission checks. Reviews code for exploitable vulnerabilities.
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
|
-
color:
|
|
5
|
+
color: info
|
|
6
6
|
mode: subagent
|
|
7
7
|
temperature: 0.1
|
|
8
8
|
---
|
package/agents/workflow/lint.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pr-comment-resolver
|
|
3
3
|
description: "Evaluates and resolves one or more related PR review threads -- assesses validity, implements fixes, and returns structured summaries with reply text. Spawned by the resolve-pr-feedback skill."
|
|
4
|
-
color:
|
|
4
|
+
color: info
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
You resolve PR review threads. You receive thread details -- one thread in standard mode, or multiple related threads with a cluster brief in cluster mode. Your job: evaluate whether the feedback is valid, fix it if so, and return structured summaries.
|
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";
|
|
@@ -79,6 +79,14 @@ ${toolMapping}
|
|
|
79
79
|
// src/lib/agent-overlays.ts
|
|
80
80
|
import fs2 from "fs";
|
|
81
81
|
import path2 from "path";
|
|
82
|
+
var SOURCE_CATEGORY_MODEL_DEFAULTS = {
|
|
83
|
+
design: "openai/gpt-5.5",
|
|
84
|
+
docs: "openai/gpt-5.4-mini",
|
|
85
|
+
"document-review": "anthropic/claude-opus-4.7",
|
|
86
|
+
research: "openai/gpt-5.5",
|
|
87
|
+
review: "anthropic/claude-opus-4.7",
|
|
88
|
+
workflow: "openai/gpt-5.4-mini"
|
|
89
|
+
};
|
|
82
90
|
var ALLOWED_OVERLAY_FIELDS = new Set([
|
|
83
91
|
"model",
|
|
84
92
|
"variant",
|
|
@@ -158,6 +166,23 @@ function inferBuiltInTemperature(name, description) {
|
|
|
158
166
|
}
|
|
159
167
|
return 0.3;
|
|
160
168
|
}
|
|
169
|
+
function getSourceCategoryModel(category) {
|
|
170
|
+
if (!category)
|
|
171
|
+
return;
|
|
172
|
+
return SOURCE_CATEGORY_MODEL_DEFAULTS[category];
|
|
173
|
+
}
|
|
174
|
+
function assertSourceCategoryModelCoverage(categories) {
|
|
175
|
+
validateSourceCategoryModelDefaults();
|
|
176
|
+
const missingCategories = categories.filter((category) => !Object.hasOwn(SOURCE_CATEGORY_MODEL_DEFAULTS, category));
|
|
177
|
+
if (missingCategories.length > 0) {
|
|
178
|
+
throw new Error(`Source category model defaults missing intentional coverage for: ${missingCategories.join(", ")}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function validateSourceCategoryModelDefaults(defaults = SOURCE_CATEGORY_MODEL_DEFAULTS) {
|
|
182
|
+
for (const [category, model] of Object.entries(defaults)) {
|
|
183
|
+
validateModel("source category model defaults", `source category model defaults.${category}`, model);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
161
186
|
function validateExactAgentOverlays(inventory, overlays, nativeAgents, enabledSkills) {
|
|
162
187
|
const result = [];
|
|
163
188
|
const seenTargets = new Map;
|
|
@@ -227,6 +252,8 @@ function hasPermissionSkill(permission) {
|
|
|
227
252
|
function validateOverlayFieldValue(sourcePath, keyPath, field, value, enabledSkills) {
|
|
228
253
|
switch (field) {
|
|
229
254
|
case "model":
|
|
255
|
+
if (value === null)
|
|
256
|
+
return;
|
|
230
257
|
validateModel(sourcePath, keyPath, value);
|
|
231
258
|
return;
|
|
232
259
|
case "variant":
|
|
@@ -571,6 +598,12 @@ function applyAgentOverlays(config, agentInfo, overlays) {
|
|
|
571
598
|
addPermissionRules(permissionRules, config.permission);
|
|
572
599
|
}
|
|
573
600
|
result.temperature = inferBuiltInTemperature(agentInfo.name, result.description);
|
|
601
|
+
if (agentInfo.category) {
|
|
602
|
+
const sourceModel = getSourceCategoryModel(agentInfo.category);
|
|
603
|
+
if (sourceModel) {
|
|
604
|
+
result.model = sourceModel;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
574
607
|
if (categoryOverlay) {
|
|
575
608
|
applyOverlayObject(result, categoryOverlay.value, permissionRules);
|
|
576
609
|
}
|
|
@@ -603,7 +636,11 @@ var OVERLAY_ASSIGN_FIELDS = [
|
|
|
603
636
|
function applyOverlayObject(target, overlay, permissionRules) {
|
|
604
637
|
for (const field of OVERLAY_ASSIGN_FIELDS) {
|
|
605
638
|
if (Object.hasOwn(overlay, field)) {
|
|
606
|
-
|
|
639
|
+
if (field === "model" && overlay[field] === null) {
|
|
640
|
+
delete target[field];
|
|
641
|
+
} else {
|
|
642
|
+
target[field] = overlay[field];
|
|
643
|
+
}
|
|
607
644
|
}
|
|
608
645
|
}
|
|
609
646
|
if (isRecord(overlay.permission)) {
|
|
@@ -699,6 +736,7 @@ function createConfigHandler(deps) {
|
|
|
699
736
|
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
700
737
|
const enabledSkillNames = collectEnabledSkillNames(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
701
738
|
const inventory = buildBundledAgentInventory(bundledAgentsDir, systematicConfig.disabled_agents);
|
|
739
|
+
assertSourceCategoryModelCoverage(inventory.categories);
|
|
702
740
|
const validatedOverlays = validateAgentOverlays({
|
|
703
741
|
inventory,
|
|
704
742
|
overlays,
|
|
@@ -734,28 +772,20 @@ function registerSkillsPaths(config, skillsDir) {
|
|
|
734
772
|
var SINGLETON_KEY = Symbol.for("systematic.singleton.v1");
|
|
735
773
|
async function plugInOnce({
|
|
736
774
|
doInit,
|
|
737
|
-
onDuplicate,
|
|
738
775
|
pid
|
|
739
776
|
}) {
|
|
740
777
|
const currentPid = pid ?? process.pid;
|
|
741
778
|
const g = globalThis;
|
|
742
779
|
const existing = g[SINGLETON_KEY];
|
|
743
780
|
if (existing && existing.pid === currentPid) {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
try {
|
|
747
|
-
onDuplicate?.(currentPid);
|
|
748
|
-
} catch {}
|
|
749
|
-
}
|
|
750
|
-
await existing.hooksPromise;
|
|
751
|
-
return { isFirst: false, hooks: {} };
|
|
781
|
+
const hooks2 = await existing.hooksPromise;
|
|
782
|
+
return { isFirst: false, hooks: hooks2 };
|
|
752
783
|
}
|
|
753
784
|
const hooksPromise = doInit();
|
|
754
785
|
g[SINGLETON_KEY] = {
|
|
755
786
|
pid: currentPid,
|
|
756
787
|
loadedAt: Date.now(),
|
|
757
|
-
hooksPromise
|
|
758
|
-
warned: false
|
|
788
|
+
hooksPromise
|
|
759
789
|
};
|
|
760
790
|
const hooks = await hooksPromise;
|
|
761
791
|
return { isFirst: true, hooks };
|
|
@@ -1004,18 +1034,7 @@ var initializePlugin = async ({ client, directory }) => {
|
|
|
1004
1034
|
};
|
|
1005
1035
|
var SystematicPlugin = async (input) => {
|
|
1006
1036
|
const { hooks } = await plugInOnce({
|
|
1007
|
-
doInit: () => initializePlugin(input)
|
|
1008
|
-
onDuplicate: (pid) => {
|
|
1009
|
-
const message = `[systematic] duplicate factory invocation in same process (pid=${pid}); skipping duplicate registration. Multiple opencode.json sources may list this plugin.`;
|
|
1010
|
-
console.warn(message);
|
|
1011
|
-
input.client.app.log({
|
|
1012
|
-
body: {
|
|
1013
|
-
service: "systematic",
|
|
1014
|
-
level: "warn",
|
|
1015
|
-
message
|
|
1016
|
-
}
|
|
1017
|
-
}).catch(() => {});
|
|
1018
|
-
}
|
|
1037
|
+
doInit: () => initializePlugin(input)
|
|
1019
1038
|
});
|
|
1020
1039
|
return hooks;
|
|
1021
1040
|
};
|
|
@@ -42,3 +42,6 @@ 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
|
+
export declare function getSourceCategoryModel(category: string | undefined): string | undefined;
|
|
46
|
+
export declare function assertSourceCategoryModelCoverage(categories: string[]): void;
|
|
47
|
+
export declare function validateSourceCategoryModelDefaults(defaults?: Record<string, unknown>): void;
|
|
@@ -11,25 +11,17 @@
|
|
|
11
11
|
*
|
|
12
12
|
* On the first invocation `doInit` runs and the resulting hooks promise
|
|
13
13
|
* is cached on `globalThis`; the caller receives `{ isFirst: true, hooks }`.
|
|
14
|
-
* On every subsequent invocation in the same PID `doInit` is skipped
|
|
15
|
-
* cached
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
*
|
|
19
|
-
* explicit PID check adds defensive belt-and-suspenders against any
|
|
20
|
-
* state-leakage edge case.
|
|
14
|
+
* On every subsequent invocation in the same PID `doInit` is skipped and
|
|
15
|
+
* the cached resolved hooks are returned directly to every caller.
|
|
16
|
+
* Across PIDs the guard is treated as absent and init runs fresh —
|
|
17
|
+
* `globalThis` is per-process, but the explicit PID check adds defensive
|
|
18
|
+
* belt-and-suspenders against any state-leakage edge case.
|
|
21
19
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* the duplicate path is the only shape that prevents host-side double
|
|
28
|
-
* registration of `systematic_skill`, the `config` hook, and the
|
|
29
|
-
* `experimental.chat.system.transform` hook. The Phase 0 follow-up probe
|
|
30
|
-
* (see `docs/plans/2026-05-01-001-fix-idempotent-plugin-registration-plan.md`)
|
|
31
|
-
* verified the duplicate `systematic_skill` entry in OpenCode's
|
|
32
|
-
* `client.tool.list(...)` output, which is the LLM-visible tool catalog.
|
|
20
|
+
* The singleton returns the same hooks reference to every caller within a
|
|
21
|
+
* process — first and duplicate alike. OpenCode may register the same hook
|
|
22
|
+
* surface once per configured plugin source; that is preferable to suppressing
|
|
23
|
+
* duplicates with an empty object because every source keeps the full tools,
|
|
24
|
+
* commands, skills, and hooks surface.
|
|
33
25
|
*
|
|
34
26
|
* **Known limitation — rejected init is sticky.** If `doInit()` rejects,
|
|
35
27
|
* the rejected promise is stored on `globalThis` and every subsequent
|
|
@@ -40,37 +32,27 @@
|
|
|
40
32
|
export interface PlugInOnceOptions<T> {
|
|
41
33
|
/** Heavy init work that should run at most once per process. */
|
|
42
34
|
doInit: () => Promise<T>;
|
|
43
|
-
/**
|
|
44
|
-
* Called exactly once on the first duplicate invocation in the same
|
|
45
|
-
* process. Subsequent duplicates are silent. Receives the same `pid`
|
|
46
|
-
* value the guard used for its identity check (so test overrides flow
|
|
47
|
-
* through faithfully). Implementations must not throw; fire-and-forget
|
|
48
|
-
* side effects (logging, metrics) are expected. Synchronous exceptions
|
|
49
|
-
* are swallowed defensively.
|
|
50
|
-
*/
|
|
51
|
-
onDuplicate?: (pid: number) => void;
|
|
52
35
|
/** Test override; defaults to `process.pid`. */
|
|
53
36
|
pid?: number;
|
|
54
37
|
}
|
|
55
38
|
/**
|
|
56
39
|
* Result envelope for `plugInOnce(...)`.
|
|
57
40
|
*
|
|
58
|
-
* - `isFirst: true` — caller
|
|
59
|
-
* - `isFirst: false` —
|
|
60
|
-
* so the host loader does not register tools or hooks twice.
|
|
41
|
+
* - `isFirst: true` — caller was the first invocation in this process.
|
|
42
|
+
* - `isFirst: false` — `doInit` was skipped; the cached result is returned.
|
|
61
43
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
44
|
+
* In both cases `hooks` is the real resolved value of `doInit()`. Callers
|
|
45
|
+
* return `result.hooks` unconditionally without inspecting `isFirst`.
|
|
64
46
|
*/
|
|
65
47
|
export interface PlugInOnceResult<T> {
|
|
66
48
|
isFirst: boolean;
|
|
67
49
|
hooks: T;
|
|
68
50
|
}
|
|
69
51
|
/**
|
|
70
|
-
* Run `doInit` at most once per process; on duplicate invocations
|
|
71
|
-
*
|
|
52
|
+
* Run `doInit` at most once per process; on duplicate invocations return the
|
|
53
|
+
* cached real hook surface so every config source sees the same surface.
|
|
72
54
|
*/
|
|
73
|
-
export declare function plugInOnce<T>({ doInit,
|
|
55
|
+
export declare function plugInOnce<T>({ doInit, pid, }: PlugInOnceOptions<T>): Promise<PlugInOnceResult<T>>;
|
|
74
56
|
/**
|
|
75
57
|
* Test-only: clear the singleton state so the next invocation re-runs
|
|
76
58
|
* init. Must not be called in production code paths.
|
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
|
|