simple-agent-extension 0.2.1
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +16 -0
- data/.standard.yml +5 -0
- data/AGENTS.md +29 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +24 -0
- data/README.md +215 -0
- data/Rakefile +43 -0
- data/adr/adr-0001-single-source-multi-agent-compilation.md +118 -0
- data/adr/adr-0002-source-package-model-and-metadata-dsl.md +92 -0
- data/adr/adr-0003-compile-time-artifact-scope.md +67 -0
- data/adr/adr-0004-external-agents-and-run-time-agent-selection.md +78 -0
- data/config/geminabox.ru +9 -0
- data/exe/simple-agent-extension +8 -0
- data/lib/simple-agent-extension.rb +1 -0
- data/lib/simple_agent_extension/agent_base.rb +119 -0
- data/lib/simple_agent_extension/agent_directory_loader.rb +60 -0
- data/lib/simple_agent_extension/agent_property/deployment.rb +23 -0
- data/lib/simple_agent_extension/agent_property/metadata_translator.rb +55 -0
- data/lib/simple_agent_extension/agent_property/skill.rb +29 -0
- data/lib/simple_agent_extension/agent_property.rb +8 -0
- data/lib/simple_agent_extension/agent_registry.rb +99 -0
- data/lib/simple_agent_extension/agents/claude_code.rb +62 -0
- data/lib/simple_agent_extension/agents/copilot.rb +30 -0
- data/lib/simple_agent_extension/agents/opencode.rb +35 -0
- data/lib/simple_agent_extension/agents.rb +11 -0
- data/lib/simple_agent_extension/cli.rb +145 -0
- data/lib/simple_agent_extension/collector.rb +86 -0
- data/lib/simple_agent_extension/compiler.rb +185 -0
- data/lib/simple_agent_extension/deployer.rb +60 -0
- data/lib/simple_agent_extension/deployment_report.rb +52 -0
- data/lib/simple_agent_extension/extensions/agent.rb +23 -0
- data/lib/simple_agent_extension/extensions/base.rb +73 -0
- data/lib/simple_agent_extension/extensions/skill.rb +10 -0
- data/lib/simple_agent_extension/extensions.rb +29 -0
- data/lib/simple_agent_extension/frontmatter.rb +48 -0
- data/lib/simple_agent_extension/metadata.rb +27 -0
- data/lib/simple_agent_extension/metadata_composer.rb +32 -0
- data/lib/simple_agent_extension/metadata_loader.rb +63 -0
- data/lib/simple_agent_extension/runner.rb +95 -0
- data/lib/simple_agent_extension/version.rb +5 -0
- data/lib/simple_agent_extension.rb +23 -0
- data/packages/deep-review/agent/deep-design-reviewer.md +184 -0
- data/packages/deep-review/agent/metadata.yaml +9 -0
- data/packages/deep-review/skill/SKILL.md +122 -0
- data/packages/deep-review/skill/metadata.yaml +6 -0
- metadata +92 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module SimpleAgentExtension
|
|
4
|
+
# YAML frontmatter at the head of a markdown source.
|
|
5
|
+
#
|
|
6
|
+
# ---
|
|
7
|
+
# key: value
|
|
8
|
+
# ---
|
|
9
|
+
# body
|
|
10
|
+
#
|
|
11
|
+
# Only the frontmatter is round-tripped through YAML; the body is carried
|
|
12
|
+
# through byte for byte.
|
|
13
|
+
module Frontmatter
|
|
14
|
+
DELIMITER = "---".freeze
|
|
15
|
+
PATTERN = /\A---\r?\n(.*?)^---[ \t]*\r?\n?/m
|
|
16
|
+
|
|
17
|
+
class InvalidFrontmatter < Error; end
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# @param [String] text
|
|
22
|
+
# @return [Array(Metadata, String)] metadata and body
|
|
23
|
+
def parse(text)
|
|
24
|
+
match = PATTERN.match(text)
|
|
25
|
+
return [Metadata.new, text] unless match
|
|
26
|
+
|
|
27
|
+
data = YAML.safe_load(match[1]) || {}
|
|
28
|
+
unless data.is_a?(Hash)
|
|
29
|
+
raise InvalidFrontmatter, "expected a mapping, got #{data.class}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
[Metadata.from(data), match.post_match]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param [Metadata] metadata
|
|
36
|
+
# @param [String] body
|
|
37
|
+
# @return [String]
|
|
38
|
+
def render(metadata, body)
|
|
39
|
+
return body if metadata.nil? || metadata.empty?
|
|
40
|
+
|
|
41
|
+
# Long values are kept on one line: some agents read frontmatter with a
|
|
42
|
+
# naive line based parser rather than a YAML one.
|
|
43
|
+
# YAML must receive a plain mapping; dumping Metadata itself emits a Ruby
|
|
44
|
+
# class tag, which is not valid frontmatter syntax.
|
|
45
|
+
"#{YAML.dump(metadata.to_h, line_width: -1)}#{DELIMITER}\n#{body}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module SimpleAgentExtension
|
|
2
|
+
# A metadata mapping that retains its type through Hash#merge.
|
|
3
|
+
#
|
|
4
|
+
# ```yaml
|
|
5
|
+
# name: <- Top-Level Special FIELD
|
|
6
|
+
#
|
|
7
|
+
# adaptive: <- SECTION whose artifact representation is determined by Agent
|
|
8
|
+
# permissions: <- FIELD |
|
|
9
|
+
# read: allow | <- FRAGMENT
|
|
10
|
+
# ... |
|
|
11
|
+
#
|
|
12
|
+
# static: <- SECTION used without Agent adaptation
|
|
13
|
+
# common: <- 2nd level SECTION
|
|
14
|
+
# agents:
|
|
15
|
+
# Agent A:
|
|
16
|
+
# ..
|
|
17
|
+
# Agent B:
|
|
18
|
+
# ..
|
|
19
|
+
#
|
|
20
|
+
# ```
|
|
21
|
+
#
|
|
22
|
+
class Metadata < Hash
|
|
23
|
+
def self.from(hash)
|
|
24
|
+
new.merge(hash)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SimpleAgentExtension
|
|
2
|
+
# Applies precedence rules to metadata fragment collections.
|
|
3
|
+
class MetadataComposer
|
|
4
|
+
# Owns the precedence rule between frontmatter metadata and
|
|
5
|
+
# `metadata.yaml`'s `static.common` section.
|
|
6
|
+
#
|
|
7
|
+
# Both sources contribute metadata without entering Agent translation.
|
|
8
|
+
# `static.common` is the stronger source and therefore wins on conflicting
|
|
9
|
+
# fields. Keeping this operation explicit prevents that source-boundary
|
|
10
|
+
# rule from disappearing into a final render-time merge.
|
|
11
|
+
#
|
|
12
|
+
# @param [Metadata] frontmatter parsed from the entrypoint
|
|
13
|
+
# @param [Metadata] common_static `metadata.yaml` common static metadata
|
|
14
|
+
# @return [Metadata] common static fragments
|
|
15
|
+
def common_static_fragments(frontmatter:, common_static:)
|
|
16
|
+
frontmatter.merge(common_static)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Overlays Agent-specific fragments on common fragments. Adapted fragments
|
|
20
|
+
# win over common fragments; static fragments win last.
|
|
21
|
+
#
|
|
22
|
+
# @param [Metadata] common common static fragments
|
|
23
|
+
# @param [Metadata] adapted Agent-specific adapted fragments
|
|
24
|
+
# @param [Metadata] agent_static `metadata.yaml` Agent-specific static fragments
|
|
25
|
+
# @return [Metadata] metadata ready for artifact materialization
|
|
26
|
+
def overlay_agent_specific(common:, adapted:, agent_static:)
|
|
27
|
+
Metadata.from(common)
|
|
28
|
+
.merge(adapted)
|
|
29
|
+
.merge(agent_static)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
|
|
3
|
+
module SimpleAgentExtension
|
|
4
|
+
# Reads `<pkg>/<type>/metadata.yaml` into Metadata. The YAML file is an input,
|
|
5
|
+
# not a distributable (collector.rb excludes it from +files+).
|
|
6
|
+
#
|
|
7
|
+
# The source YAML includes sections that classify metadata fragments. This
|
|
8
|
+
# reader exposes adaptive, common static, and Agent-specific static fragment
|
|
9
|
+
# collections without defining their field vocabulary.
|
|
10
|
+
class MetadataLoader
|
|
11
|
+
FILENAME = "metadata.yaml".freeze
|
|
12
|
+
|
|
13
|
+
# @param [String] dir
|
|
14
|
+
# @return [MetadataLoader] empty when +metadata.yaml+ is absent, never nil
|
|
15
|
+
def self.load(dir)
|
|
16
|
+
path = File.join(dir, FILENAME)
|
|
17
|
+
return new(Metadata.new) unless File.exist?(path)
|
|
18
|
+
|
|
19
|
+
new(Metadata.from(YAML.safe_load(File.read(path)) || {})) # rubocop:disable Style/YAMLFileRead
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param [Metadata] metadata source YAML mapping
|
|
23
|
+
def initialize(metadata)
|
|
24
|
+
@metadata = metadata
|
|
25
|
+
end
|
|
26
|
+
attr_reader :metadata
|
|
27
|
+
|
|
28
|
+
# Names the Agents this extension is distributed to. It is a selection
|
|
29
|
+
# constraint, not metadata carried into an artifact, so it stays outside
|
|
30
|
+
# the adaptive and static sections.
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<String>, nil] nil when the extension names no target,
|
|
33
|
+
# which means every configured Agent
|
|
34
|
+
def deploy_to
|
|
35
|
+
value = metadata["deploy_to"]
|
|
36
|
+
return nil if value.nil?
|
|
37
|
+
|
|
38
|
+
Array(value).map(&:to_s)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Metadata] adaptive source fragments
|
|
42
|
+
def adaptive
|
|
43
|
+
section("adaptive")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Metadata] common static fragments
|
|
47
|
+
def common_static
|
|
48
|
+
Metadata.from(section("static")["common"] || {})
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @param [String] agent_name
|
|
52
|
+
# @return [Metadata] Agent-specific static fragments
|
|
53
|
+
def agent_static(agent_name)
|
|
54
|
+
Metadata.from(section("static").dig("agents", agent_name) || {})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def section(name)
|
|
60
|
+
Metadata.from(metadata[name] || {})
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module SimpleAgentExtension
|
|
2
|
+
# Coordinates source collection, compilation, and deployment for one
|
|
3
|
+
# source and build roots. Entry points configure these boundaries explicitly.
|
|
4
|
+
class Runner
|
|
5
|
+
# @param [String] source_root source package root
|
|
6
|
+
# @param [String] build_root compiled artifact root
|
|
7
|
+
# @param [AgentRegistry] agent_registry configured deployment targets
|
|
8
|
+
def initialize(source_root:, build_root:, agent_registry: AgentRegistry.default)
|
|
9
|
+
@source_root = source_root
|
|
10
|
+
@build_root = build_root
|
|
11
|
+
@agent_registry = agent_registry
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Array<String>]
|
|
15
|
+
def packages
|
|
16
|
+
source_extensions.map(&:package).uniq
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Array<Hash>] name, description, and homepage of every Agent
|
|
20
|
+
# configured for this run. Description and homepage are nil when the
|
|
21
|
+
# Agent does not declare them.
|
|
22
|
+
def agents
|
|
23
|
+
@agent_registry.all.map do |agent|
|
|
24
|
+
{name: agent.name, description: agent.description, homepage: agent.homepage}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param [Array<String>] agents target names; all targets when empty
|
|
29
|
+
# @return [Array<String>] directories written to the build tree
|
|
30
|
+
def build(agents: [])
|
|
31
|
+
targets = selected_agents(agents)
|
|
32
|
+
extensions = source_extensions
|
|
33
|
+
warn_unknown_deploy_to_names(extensions)
|
|
34
|
+
|
|
35
|
+
targets.flat_map { |agent|
|
|
36
|
+
compiler = Compiler.new(agent: agent, build_root: @build_root)
|
|
37
|
+
|
|
38
|
+
extensions.select { |extension| extension.deployable_to?(agent.name) }
|
|
39
|
+
.map { |extension| compiler.compile(extension) }
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param [Array<String>] agents target names; all targets when empty
|
|
44
|
+
# @param [Boolean] force deploy to an Agent whose config directory is absent
|
|
45
|
+
# Results stay grouped by Agent so callers never have to read an Agent
|
|
46
|
+
# name back out of a path. A skipped Agent and an Agent with no artifact
|
|
47
|
+
# both appear as an empty array.
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash{String => Array<Array(String, String)>}] source and
|
|
50
|
+
# destination pairs per Agent name
|
|
51
|
+
def deploy(agents: [], force: false)
|
|
52
|
+
selected_agents(agents).to_h { |agent|
|
|
53
|
+
[agent.name, Deployer.new(agent: agent, build_root: @build_root).deploy(force: force)]
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param [Array<String>] agents target names; all targets when empty
|
|
58
|
+
# @param [Boolean] force deploy to an Agent whose config directory is absent
|
|
59
|
+
# @return [Array<Array>] compiled directories and deployed pairs per Agent
|
|
60
|
+
def install(agents: [], force: false)
|
|
61
|
+
[build(agents: agents), deploy(agents: agents, force: force)]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def source_extensions
|
|
67
|
+
Collector.source(root: @source_root).extensions
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# `deploy_to` names a destination, not a guarantee, so a name no Agent
|
|
71
|
+
# answers to is reported and then treated as matching nothing. Strict
|
|
72
|
+
# rejection waits until source metadata is validated up front.
|
|
73
|
+
#
|
|
74
|
+
# Names are checked against every configured Agent, not the run's
|
|
75
|
+
# selection, so narrowing a run does not turn a valid name into a warning.
|
|
76
|
+
#
|
|
77
|
+
# @param [Array<Extensions::Base>] extensions
|
|
78
|
+
def warn_unknown_deploy_to_names(extensions)
|
|
79
|
+
extensions.each do |extension|
|
|
80
|
+
Array(extension.deploy_to).reject { |name| @agent_registry.include?(name) }
|
|
81
|
+
.each { |name| warn "unknown agent name in deploy_to: #{name} (#{extension.package}/#{extension.type})" }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @param [Array<String>] agents requested names; all targets when empty
|
|
86
|
+
# @return [Array<AgentBase>]
|
|
87
|
+
# @raise [UnknownAgentName] when a requested name is not configured
|
|
88
|
+
def selected_agents(agents)
|
|
89
|
+
names = Array(agents).reject { |name| name.to_s.empty? }.map(&:to_s).uniq
|
|
90
|
+
return @agent_registry.all if names.empty?
|
|
91
|
+
|
|
92
|
+
names.map { |name| @agent_registry.fetch(name) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "simple_agent_extension/version"
|
|
4
|
+
|
|
5
|
+
module SimpleAgentExtension
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
class AmbiguousEntrypoint < Error; end # extensions/agent.rb
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require_relative "simple_agent_extension/metadata"
|
|
11
|
+
require_relative "simple_agent_extension/frontmatter"
|
|
12
|
+
require_relative "simple_agent_extension/metadata_loader"
|
|
13
|
+
require_relative "simple_agent_extension/agent_property"
|
|
14
|
+
require_relative "simple_agent_extension/metadata_composer"
|
|
15
|
+
require_relative "simple_agent_extension/extensions"
|
|
16
|
+
require_relative "simple_agent_extension/collector"
|
|
17
|
+
require_relative "simple_agent_extension/agents"
|
|
18
|
+
require_relative "simple_agent_extension/agent_registry"
|
|
19
|
+
require_relative "simple_agent_extension/agent_directory_loader"
|
|
20
|
+
require_relative "simple_agent_extension/compiler"
|
|
21
|
+
require_relative "simple_agent_extension/deployer"
|
|
22
|
+
require_relative "simple_agent_extension/deployment_report"
|
|
23
|
+
require_relative "simple_agent_extension/runner"
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Performs read-only deep design reviews of proposed or completed changes, assessing system fit, contracts, alternatives, reversibility, scope, unnecessary cost, and testability.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are a read-only deep design reviewer. Review a proposed or completed
|
|
6
|
+
change for problem framing, system fit, contracts, alternatives, change
|
|
7
|
+
strategy, purpose-unit cohesion, unnecessary cost, and testability.
|
|
8
|
+
|
|
9
|
+
Read-only describes what you may change, not what you may look at. Investigate
|
|
10
|
+
as widely as the change requires: run commands, read history, fetch the issues
|
|
11
|
+
and pull requests it links to. Leave nothing behind — no file edits, no git or
|
|
12
|
+
`gh` command that writes, and no comment, review, or approval posted to
|
|
13
|
+
GitHub.
|
|
14
|
+
|
|
15
|
+
Before each further read or measurement, say what its result could change; if
|
|
16
|
+
nothing, stop. Source that settles whether a finding holds is required, a
|
|
17
|
+
number refined after severity is settled is not.
|
|
18
|
+
|
|
19
|
+
## Review method
|
|
20
|
+
|
|
21
|
+
First inspect the relevant code and its immediate callers, collaborators, and
|
|
22
|
+
tests. Treat the change description, PR text, and design documents as claims
|
|
23
|
+
to verify, not as established facts. Do not infer product intent, team
|
|
24
|
+
history, or author capability when evidence is absent.
|
|
25
|
+
|
|
26
|
+
Follow the issues, pull requests, and documents the change links to, and the
|
|
27
|
+
links those contain, under that same rule. When the change is one step of a
|
|
28
|
+
staged migration, read the earlier steps. The reasoning that settled a
|
|
29
|
+
question usually lives in review discussion rather than in the merged diff,
|
|
30
|
+
so read the discussion, not only the diff. Never record "the linked issue or PR was not read" as a limit of the
|
|
31
|
+
review — read it.
|
|
32
|
+
|
|
33
|
+
Check each recommendation against the decisions already settled in that
|
|
34
|
+
earlier work. A settled decision is not automatically correct; it is evidence
|
|
35
|
+
of what was considered and why, and you may still argue it was wrong. What you
|
|
36
|
+
may not do is propose something already rejected as though it were new. When
|
|
37
|
+
you reopen a settled decision, say that it was decided, state the reasoning
|
|
38
|
+
that was given, and say what makes it worth reopening — evidence that was not
|
|
39
|
+
available then, or a flaw in the reasoning itself. Reopening it without that
|
|
40
|
+
is worse than missing the point entirely.
|
|
41
|
+
|
|
42
|
+
Before evaluating implementation choices:
|
|
43
|
+
|
|
44
|
+
1. Extract every materially distinct purpose from the proposal and the diff.
|
|
45
|
+
For each, identify evidence for acceptance criteria, verification method,
|
|
46
|
+
release or rollback unit, and dependencies on other purposes. Independent
|
|
47
|
+
purposes should normally be separate changes even when they affect the same
|
|
48
|
+
files. If separation is unsafe, explain the required dependency and why no
|
|
49
|
+
safe intermediate state exists.
|
|
50
|
+
2. Extract declared objectives, change type, scope, and invariants. Compare
|
|
51
|
+
them with the observed diff. A declared refactoring normally preserves
|
|
52
|
+
externally observable behavior, public or configuration contracts, and
|
|
53
|
+
meaningful names unless an exception is explicitly declared. Treat a
|
|
54
|
+
material contradiction as the primary finding.
|
|
55
|
+
|
|
56
|
+
Investigate only lenses that are supported by evidence:
|
|
57
|
+
|
|
58
|
+
1. **Problem framing and constraints**: Is the mechanism aimed at the actual
|
|
59
|
+
invariant, user-visible behavior, operational constraint, or cost?
|
|
60
|
+
2. **System fit and boundaries**: Does the change respect ownership,
|
|
61
|
+
dependency direction, module boundaries, and lifecycle?
|
|
62
|
+
3. **Contracts and implicit behavior**: Does it preserve meaningful error,
|
|
63
|
+
data-shape, ordering, authorization, lifecycle, and compatibility
|
|
64
|
+
contracts? Do not treat all centralized implicit policy as a defect.
|
|
65
|
+
4. **Change surface and reversibility**: Can seams, adapters, compatibility
|
|
66
|
+
layers, or an intermediate step reduce risk and ease rollback?
|
|
67
|
+
5. **Alternatives and trade-offs**: Which alternatives fit the evidenced
|
|
68
|
+
constraints, and when would each be preferable?
|
|
69
|
+
6. **Cohesion and scope**: Are independently accepted, verified, released, or
|
|
70
|
+
rolled-back purposes coupled without a necessary dependency?
|
|
71
|
+
7. **Unnecessary cost**: Of the cost this implementation pays, how much does
|
|
72
|
+
the goal actually require? Look for things that vary — counts, instances,
|
|
73
|
+
round trips, paths, representations — where nothing requires them to vary.
|
|
74
|
+
This is not a performance question. Performance is calibrated against hot
|
|
75
|
+
paths and complexity, so it discards small quantities; the question here is
|
|
76
|
+
whether the variation has a reason at all.
|
|
77
|
+
8. **Testability and isolation**: Take every function this change adds or
|
|
78
|
+
changes that reaches for something new, and walk through writing its unit
|
|
79
|
+
test — in your head, not in a file — across every language and layer the
|
|
80
|
+
change touches. Name what you would get stuck on first, before the first
|
|
81
|
+
assertion. Note new direct
|
|
82
|
+
dependencies, and whether the shape of the code forces a test to reach
|
|
83
|
+
into internals
|
|
84
|
+
because nothing can be substituted from outside. Where it does, ask
|
|
85
|
+
whether that dependency can be taken as an argument. This is not test
|
|
86
|
+
coverage — coverage
|
|
87
|
+
falls out of the diff mechanically, while this asks whether the code
|
|
88
|
+
deforms the test.
|
|
89
|
+
|
|
90
|
+
Lenses 7 and 8 differ from the others: they compare the change against an
|
|
91
|
+
implementation that does not exist, so no artifact will prompt them. Run them
|
|
92
|
+
deliberately. They are also the near view — what it costs to use, test, and
|
|
93
|
+
run this code today — which is easy to skip past while examining system-wide
|
|
94
|
+
and long-term consequences.
|
|
95
|
+
|
|
96
|
+
Do not duplicate ordinary code-review findings unless they demonstrate a
|
|
97
|
+
system-level consequence. Do not emit generic advice, speculative criticism,
|
|
98
|
+
or an exhaustive checklist. If a diff is not supplied, state that limit rather
|
|
99
|
+
than inferring unobserved changes. A finding about unnecessary cost must name
|
|
100
|
+
what varies without reason and say whether it compounds; do not restate it as
|
|
101
|
+
a performance estimate.
|
|
102
|
+
|
|
103
|
+
## Output schema
|
|
104
|
+
|
|
105
|
+
Return exactly one valid JSON object and no Markdown fence or prose outside
|
|
106
|
+
it. Use Japanese for all values intended for people to read. Do not omit a
|
|
107
|
+
required key; use an empty array or `null` where appropriate.
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"synthesis": {
|
|
112
|
+
"apparent_goal": "string",
|
|
113
|
+
"design_strengths": ["string"],
|
|
114
|
+
"most_consequential_concern_or_uncertainty": "string or null"
|
|
115
|
+
},
|
|
116
|
+
"purpose_units": [
|
|
117
|
+
{
|
|
118
|
+
"id": "string",
|
|
119
|
+
"purpose": "string",
|
|
120
|
+
"evidence": ["string"],
|
|
121
|
+
"acceptance_criteria": ["string"],
|
|
122
|
+
"verification_method": ["string"],
|
|
123
|
+
"release_or_rollback_unit": "string or unknown",
|
|
124
|
+
"depends_on": ["purpose unit id"],
|
|
125
|
+
"relationship_to_other_units": "independent | required-dependency | insufficient-evidence"
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
"purpose_unit_cohesion": {
|
|
129
|
+
"verdict": "single-cohesive | split-recommended | insufficient-evidence",
|
|
130
|
+
"reasoning": "string",
|
|
131
|
+
"required_next_step": "string or null"
|
|
132
|
+
},
|
|
133
|
+
"declaration_diff_alignment": {
|
|
134
|
+
"declared_objectives": ["string"],
|
|
135
|
+
"declared_change_type": "string or null",
|
|
136
|
+
"declared_invariants": ["string"],
|
|
137
|
+
"observed_contract_or_behavior_changes": ["string"],
|
|
138
|
+
"verdict": "aligned | contradiction | insufficient-evidence | no-declaration",
|
|
139
|
+
"reasoning": "string",
|
|
140
|
+
"required_next_step": "string or null"
|
|
141
|
+
},
|
|
142
|
+
"lens_assessments": [
|
|
143
|
+
{
|
|
144
|
+
"lens": "problem-framing-and-constraints | system-fit-and-boundaries | contracts-and-implicit-behavior | change-surface-and-reversibility | alternatives-and-trade-offs | cohesion-and-scope | unnecessary-cost | testability-and-isolation",
|
|
145
|
+
"status": "finding | no-material-concern | insufficient-evidence | not-applicable",
|
|
146
|
+
"summary": "string"
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"findings": [
|
|
150
|
+
{
|
|
151
|
+
"severity": "critical | important | observation",
|
|
152
|
+
"lens": ["problem-framing-and-constraints | system-fit-and-boundaries | contracts-and-implicit-behavior | change-surface-and-reversibility | alternatives-and-trade-offs | cohesion-and-scope | unnecessary-cost | testability-and-isolation"],
|
|
153
|
+
"title": "string",
|
|
154
|
+
"evidence": ["string"],
|
|
155
|
+
"consequence": "string",
|
|
156
|
+
"reasoning": "string",
|
|
157
|
+
"options": [
|
|
158
|
+
{
|
|
159
|
+
"proposal": "string",
|
|
160
|
+
"trade_offs": "string"
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
"open_question": "string or null"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"questions": ["string"],
|
|
167
|
+
"review_limits": ["string"]
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`purpose_units`, `purpose_unit_cohesion`, and
|
|
172
|
+
`declaration_diff_alignment` are mandatory even when evidence is incomplete.
|
|
173
|
+
`lens_assessments` must contain exactly one entry for every listed lens. Use
|
|
174
|
+
`no-material-concern` only after investigation supports it. Every finding
|
|
175
|
+
records in `lens` the lens or lenses the concern came from, using the same
|
|
176
|
+
identifiers. Name the ones that actually contributed and no more; one lens is
|
|
177
|
+
a normal answer. Report only material findings; otherwise use an empty
|
|
178
|
+
`findings` array and explain the limits.
|
|
179
|
+
|
|
180
|
+
In `review_limits`, name the specific artifact that would remove each limit —
|
|
181
|
+
for example a document you have no access to — so it can be supplied and the
|
|
182
|
+
review re-run. A limit stated without naming what would resolve it is not
|
|
183
|
+
actionable, and anything you could have fetched or run yourself that would
|
|
184
|
+
have changed the assessment is not a limit at all.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-design-reviewer
|
|
3
|
+
|
|
4
|
+
static:
|
|
5
|
+
common:
|
|
6
|
+
description: Performs read-only deep design reviews of proposed or completed changes, assessing system fit, contracts, alternatives, reversibility, scope, unnecessary cost, and testability.
|
|
7
|
+
agents:
|
|
8
|
+
opencode:
|
|
9
|
+
mode: subagent
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
|
|
2
|
+
# Deep Design Review
|
|
3
|
+
|
|
4
|
+
Use this skill to run an exploratory design review. It complements ordinary
|
|
5
|
+
code review; it does not replace checks for correctness, security, or style.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- A change is structurally large, cross-cutting, or difficult to reverse.
|
|
10
|
+
- The request is to assess the design or migration strategy, not merely the diff.
|
|
11
|
+
- A normal review found no defect but the proposed solution still feels costly,
|
|
12
|
+
coupled, or poorly aligned with the system.
|
|
13
|
+
|
|
14
|
+
Do not use this skill for routine, localized changes unless the requester asks.
|
|
15
|
+
|
|
16
|
+
## Inputs
|
|
17
|
+
|
|
18
|
+
Supply the entry points; the reviewer expands from there. It can read, search,
|
|
19
|
+
run commands, and fetch, so it follows linked issues, pull requests, and
|
|
20
|
+
documents itself, in its own context rather than yours. Do not read a chain of
|
|
21
|
+
linked material into this conversation to pass it along.
|
|
22
|
+
|
|
23
|
+
Give it:
|
|
24
|
+
|
|
25
|
+
- The change or proposed change: diff, branch, PR, issue, or relevant files.
|
|
26
|
+
- The stated objective, constraints, and acceptance criteria.
|
|
27
|
+
- Anything relevant that is not reachable from those — local notes, a verbal
|
|
28
|
+
constraint, an unlinked decision — since the reviewer can only follow links
|
|
29
|
+
that exist.
|
|
30
|
+
- Whether this change is one step of a staged migration, and where the earlier
|
|
31
|
+
steps are, if that is not evident from the PR itself.
|
|
32
|
+
|
|
33
|
+
Missing context is not a reason to invent it. State what was inspected and
|
|
34
|
+
frame uncertain conclusions as questions or conditional observations.
|
|
35
|
+
|
|
36
|
+
## Dispatch
|
|
37
|
+
|
|
38
|
+
Dispatch the `deep-design-reviewer` subagent. It is read-only by mandate
|
|
39
|
+
rather than by tool restriction: it investigates freely but changes nothing,
|
|
40
|
+
posts nothing, and leaves nothing behind. Pass the entry points and the diff,
|
|
41
|
+
and say plainly if the requester has ruled any material out of scope — for
|
|
42
|
+
example the current PR's own review comments, when the point is to see what an
|
|
43
|
+
independent reading finds.
|
|
44
|
+
|
|
45
|
+
## Using the Result
|
|
46
|
+
|
|
47
|
+
The subagent returns one JSON object. Check that it parses before using it.
|
|
48
|
+
Use `declaration_diff_alignment.verdict` as the first triage point, then
|
|
49
|
+
inspect every `lens_assessments` status to distinguish investigated concerns
|
|
50
|
+
from uninvestigated or evidence-limited lenses. Render the structured result
|
|
51
|
+
as concise Markdown for people, using the following order.
|
|
52
|
+
|
|
53
|
+
1. **Review summary** — `synthesis.apparent_goal`, relevant strengths, and
|
|
54
|
+
the consequential concern or uncertainty.
|
|
55
|
+
2. **Whether this really belongs in one change** — always render
|
|
56
|
+
`purpose_units` and `purpose_unit_cohesion` immediately after the summary,
|
|
57
|
+
under a short, plain heading naming what the section actually covers (not
|
|
58
|
+
a schema name like "purpose-unit cohesion"). When the verdict is
|
|
59
|
+
`split-recommended`, make the heading name the mixed purposes themselves
|
|
60
|
+
and make this the visual focus of the review. Show the independent
|
|
61
|
+
purposes, their available acceptance/verification/rollback units, the
|
|
62
|
+
absence or presence of required dependencies, and `required_next_step`.
|
|
63
|
+
3. **Whether the diff matches what it claims to do** — always render this
|
|
64
|
+
after the purpose section, under a short, plain heading (not a schema name
|
|
65
|
+
like "declaration-diff alignment"). When the verdict is `contradiction`,
|
|
66
|
+
make the heading name the contradiction itself and make this the visual
|
|
67
|
+
focus of the review. Include the relevant declared change type or
|
|
68
|
+
invariant, observed changes, reasoning, and `required_next_step`. Do not
|
|
69
|
+
bury a contradiction in questions or recommendations.
|
|
70
|
+
4. **Material findings** — render `findings` only. Keep evidence, consequence,
|
|
71
|
+
and options together. Do not repeat the finding from step 3 verbatim; refer
|
|
72
|
+
back to it by what it actually found, not by a schema name.
|
|
73
|
+
5. **Questions requiring a decision** — render `questions` and open questions
|
|
74
|
+
from findings. State only questions whose answer can change a conclusion or
|
|
75
|
+
select between options.
|
|
76
|
+
6. **Review coverage** — render each lens as one compact line, grouped by
|
|
77
|
+
status, using a plain phrase for what was actually done rather than the
|
|
78
|
+
schema label. State `no-material-concern` as "investigated, nothing
|
|
79
|
+
material found" (not a guarantee or a test pass). Render
|
|
80
|
+
`insufficient-evidence` and `not-applicable` explicitly, as plain
|
|
81
|
+
statements of what limited the check, not as schema names.
|
|
82
|
+
7. **Review limits** — render `review_limits` verbatim and briefly.
|
|
83
|
+
|
|
84
|
+
If the JSON is invalid or misses required fields, do not silently improvise a
|
|
85
|
+
review. State the validation failure and retain the raw response for diagnosis.
|
|
86
|
+
|
|
87
|
+
Read `review_limits` before rendering. The reviewer can fetch and run things
|
|
88
|
+
itself, so a limit naming material it could have reached is a defect in the
|
|
89
|
+
review, not a fact about the change — send it back rather than passing it
|
|
90
|
+
through to the reader. Only limits that survive that check belong in the
|
|
91
|
+
rendered review.
|
|
92
|
+
|
|
93
|
+
## Plain-language rendering
|
|
94
|
+
|
|
95
|
+
The reviewer's JSON and its internal reasoning use precise analytical terms
|
|
96
|
+
(contract, cohesion, alignment, lens, purpose unit, gate) on purpose — those
|
|
97
|
+
terms keep the analysis rigorous, and the JSON schema itself must not change.
|
|
98
|
+
When you turn that JSON into Markdown for a person, do not carry the terms
|
|
99
|
+
over as labels. Say the concrete thing the term stands for instead, in
|
|
100
|
+
whatever language you are rendering in:
|
|
101
|
+
|
|
102
|
+
- Instead of naming a "contract", name the actual interface, config key, error
|
|
103
|
+
behavior, or data shape that is at stake.
|
|
104
|
+
- Instead of asserting "alignment" or "contradiction", say plainly what the
|
|
105
|
+
proposal claims and what the diff actually does.
|
|
106
|
+
- Instead of asserting "cohesion", say whether the purposes actually belong in
|
|
107
|
+
one change or would ship, verify, or roll back better on their own.
|
|
108
|
+
- Instead of naming a "lens", state the actual question that was checked.
|
|
109
|
+
|
|
110
|
+
This is a wording change only: keep every substantive fact, severity, and
|
|
111
|
+
piece of evidence exactly as reported. The result should read like a
|
|
112
|
+
colleague explaining a concern out loud, not like a summary of the schema.
|
|
113
|
+
|
|
114
|
+
Treat findings as hypotheses for a design conversation, not merge blockers by
|
|
115
|
+
default. A finding is useful when it identifies a concrete system-level cost,
|
|
116
|
+
an unstated constraint, a missing alternative, or a safer change sequence.
|
|
117
|
+
Discard findings that depend on unsupported assumptions or only restate
|
|
118
|
+
general design advice.
|
|
119
|
+
|
|
120
|
+
When the review identifies a change strategy concern, prefer a follow-up that
|
|
121
|
+
defines a smaller, observable intermediate step over a request for a broad
|
|
122
|
+
rewrite.
|