diogenes 0.1.7 → 0.1.8

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/skills/evaluate-feature/SKILL.md +27 -0
  3. data/.claude/skills/evaluate-feature/SKILL.md +27 -0
  4. data/.cursor/rules/diogenes.mdc +40 -0
  5. data/.diogenes/artifacts/decision_record.md.erb +44 -0
  6. data/.diogenes/diogenes.rb +13 -0
  7. data/.diogenes/hooks/README.md +15 -0
  8. data/.diogenes/rules/five_gates.rb +33 -0
  9. data/.diogenes/skills/example_skill.rb +33 -0
  10. data/.release-please-manifest.json +1 -1
  11. data/CHANGELOG.md +7 -0
  12. data/CLAUDE.md +24 -128
  13. data/lib/diogenes/build/sources.rb +8 -0
  14. data/lib/diogenes/cli/build.rb +115 -0
  15. data/lib/diogenes/cli.rb +5 -2
  16. data/lib/diogenes/configuration.rb +38 -0
  17. data/lib/diogenes/targets/base.rb +58 -0
  18. data/lib/diogenes/targets/claude_code.rb +76 -0
  19. data/lib/diogenes/targets/codex.rb +59 -0
  20. data/lib/diogenes/targets/copilot.rb +50 -0
  21. data/lib/diogenes/targets/cursor.rb +59 -0
  22. data/lib/diogenes/targets/gemini.rb +56 -0
  23. data/lib/diogenes/targets.rb +20 -0
  24. data/lib/diogenes/version.rb +1 -1
  25. data/lib/diogenes.rb +13 -0
  26. data/sig/generated/diogenes/cli/build.rbs +40 -0
  27. data/sig/generated/diogenes/configuration.rbs +22 -0
  28. data/sig/generated/diogenes/targets/base.rbs +25 -0
  29. data/sig/generated/diogenes/targets/claude_code.rbs +29 -0
  30. data/sig/generated/diogenes/targets/codex.rbs +26 -0
  31. data/sig/generated/diogenes/targets/copilot.rbs +26 -0
  32. data/sig/generated/diogenes/targets/cursor.rbs +26 -0
  33. data/sig/generated/diogenes/targets/gemini.rbs +26 -0
  34. data/sig/generated/diogenes/targets.rbs +10 -0
  35. data/sig/generated/diogenes.rbs +6 -0
  36. metadata +28 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8590686fd3f111b4286290f58a9ca7818201a3693c57bd141bf9e63eed0faecc
4
- data.tar.gz: 9cc0cd2735ca360cefc82bf7c53116f9603ac00ee738e7c3729efbd8aaf3ab9f
3
+ metadata.gz: 59e0697472a007da996d52c2c52049bd875d48a06bb4c9747e87d60aeb81977f
4
+ data.tar.gz: 9802457c4c57f7fbf9aa78a97026c96b862138e2d1f4d00fe07a6ceabf174009
5
5
  SHA512:
6
- metadata.gz: 8338868da9847a7eace4b9fa0379de833ca6d94716b97f4c71e34b06f735804d3b41ad9b7457560566487698b01f60fa828b4486a8a298c6aec7df327a8f12ce
7
- data.tar.gz: 3e9270df51a313e335513c13a2d576d43bf2900c81c270ad7e82233ea4a6df46a76b37bebbdd4b5bb151b99a9e6b630c5e222e411ec3d8b406d3242d4cbc5496
6
+ metadata.gz: be715cb566e8eb48fad63ee0a0c4b4c53d5907a45d93b6f8366fd1a5aa2b6966b37f80d88fbe6ab64789d0756b290c82d8b4dfebb00b06568a9d48a15c09d7af
7
+ data.tar.gz: 743a2837f4d2a4e6f97942d36956dc08f2b69507c04e3ea19e56e33159fb75f65263c0f92e3c8429c14a75b37ba8b8fdf4c1aff1d769e85e04797f5a38915429
@@ -0,0 +1,27 @@
1
+ # /evaluate-feature
2
+
3
+ Walk a proposed AI feature through the five Diogenes gates
4
+
5
+ ## Prompt
6
+
7
+ You are helping a Ruby developer evaluate whether a proposed AI feature
8
+ should be built. Walk them through each of the five Diogenes gates in order,
9
+ asking focused questions and surfacing the failure mode clearly if a gate fails.
10
+
11
+ The feature being evaluated: {{input}}
12
+
13
+ The five gates are:
14
+ 1. Failure Mode — What happens when this feature is wrong? Is the failure recoverable?
15
+ 2. User Verifiable — Can the average user tell when it's wrong?
16
+ 3. Human in the Loop — Is there a real human checking, with time and authority to intervene?
17
+ 4. Observability — Do you have monitoring to know when it's going wrong in production?
18
+ 5. Right Tool — Is AI the right answer, or just the exciting one?
19
+
20
+ For each gate:
21
+ - State the gate name and the Ruby principle it maps to
22
+ - Ask one focused question to determine pass or fail
23
+ - State the verdict clearly (PASS or FAIL)
24
+ - If FAIL: explain why and suggest what the right approach is instead
25
+
26
+ At the end, generate a structured decision record with a summary verdict:
27
+ PROCEED, REJECT, or PROCEED WITH CONDITIONS.
@@ -0,0 +1,27 @@
1
+ # /evaluate-feature
2
+
3
+ Walk a proposed AI feature through the five Diogenes gates
4
+
5
+ ## Prompt
6
+
7
+ You are helping a Ruby developer evaluate whether a proposed AI feature
8
+ should be built. Walk them through each of the five Diogenes gates in order,
9
+ asking focused questions and surfacing the failure mode clearly if a gate fails.
10
+
11
+ The feature being evaluated: {{input}}
12
+
13
+ The five gates are:
14
+ 1. Failure Mode — What happens when this feature is wrong? Is the failure recoverable?
15
+ 2. User Verifiable — Can the average user tell when it's wrong?
16
+ 3. Human in the Loop — Is there a real human checking, with time and authority to intervene?
17
+ 4. Observability — Do you have monitoring to know when it's going wrong in production?
18
+ 5. Right Tool — Is AI the right answer, or just the exciting one?
19
+
20
+ For each gate:
21
+ - State the gate name and the Ruby principle it maps to
22
+ - Ask one focused question to determine pass or fail
23
+ - State the verdict clearly (PASS or FAIL)
24
+ - If FAIL: explain why and suggest what the right approach is instead
25
+
26
+ At the end, generate a structured decision record with a summary verdict:
27
+ PROCEED, REJECT, or PROCEED WITH CONDITIONS.
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Diogenes AI feature decision framework
3
+ alwaysApply: true
4
+ ---
5
+
6
+ <!-- Generated by `diogenes build --target cursor`. Edit sources in `.diogenes/` and rebuild. Do not edit directly. -->
7
+
8
+ ## Rules
9
+
10
+ ### five_gates
11
+
12
+ ## The Five Diogenes Gates
13
+
14
+ When evaluating or building AI features, apply these gates in order.
15
+ A gate failure is information, not a verdict — but it must be addressed
16
+ before shipping.
17
+
18
+ **Gate 1 — Failure Mode** (Least surprise at scale)
19
+ What happens when this feature is wrong? Recoverable failures pass.
20
+ Embarrassing failures pass with conditions. Catastrophic failures do not pass.
21
+
22
+ **Gate 2 — User Verifiable** (Trust requires verification)
23
+ Can the average user tell when the output is wrong? If the user lacks
24
+ domain expertise to evaluate AI output, the feature creates a confidence gap.
25
+
26
+ **Gate 3 — Human in the Loop** (Human-centered design, genuinely)
27
+ Is there a human with time, context, and authority to actually intervene?
28
+ Rubber-stamping is not a loop.
29
+
30
+ **Gate 4 — Observability** (Craftsmanship — you wouldn't ship blind)
31
+ Do you have monitoring to detect when this feature degrades in production?
32
+ Silent degradation is worse than no feature.
33
+
34
+ **Gate 5 — Right Tool** (Convention over configuration)
35
+ Is AI the right answer, or just the exciting one? Prefer the boring,
36
+ predictable software alternative when it serves the same need.
37
+
38
+ ## Skills
39
+
40
+ - **/evaluate-feature** — Walk a proposed AI feature through the five Diogenes gates
@@ -0,0 +1,44 @@
1
+ # AI Feature Decision Record
2
+
3
+ **Feature:** <%= feature_name %>
4
+ **Date:** <%= Date.today.strftime("%Y-%m-%d") %>
5
+ **Evaluator:** <%= evaluator %>
6
+ **Verdict:** <%= verdict %>
7
+
8
+ ---
9
+
10
+ ## Gate Results
11
+
12
+ | Gate | Principle | Result | Reason |
13
+ |------|-----------|--------|--------|
14
+ | Failure Mode | Least surprise at scale | <%= gate_result(:failure_mode) %> | <%= gate_reason(:failure_mode) %> |
15
+ | User Verifiable | Trust requires verification | <%= gate_result(:user_verifiable) %> | <%= gate_reason(:user_verifiable) %> |
16
+ | Human in the Loop | Human-centered design, genuinely | <%= gate_result(:human_in_loop) %> | <%= gate_reason(:human_in_loop) %> |
17
+ | Observability | Craftsmanship — you wouldn't ship blind | <%= gate_result(:observability) %> | <%= gate_reason(:observability) %> |
18
+ | Right Tool | Convention over configuration | <%= gate_result(:right_tool) %> | <%= gate_reason(:right_tool) %> |
19
+
20
+ ---
21
+
22
+ ## Verdict: <%= verdict %>
23
+
24
+ <% if verdict == "REJECT" || verdict == "PROCEED WITH CONDITIONS" -%>
25
+ ### Recommended Alternative or Mitigation
26
+
27
+ <%= alternative.empty? ? "_No alternative or mitigation described._" : alternative %>
28
+
29
+ <% end -%>
30
+ <% if verdict == "PROCEED WITH CONDITIONS" -%>
31
+ ### Conditions for Proceeding
32
+
33
+ <%= conditions.empty? ? "_No conditions specified._" : conditions %>
34
+
35
+ <% end -%>
36
+ ---
37
+
38
+ ## Notes
39
+
40
+ <%= notes.empty? ? "_No additional notes._" : notes %>
41
+
42
+ ---
43
+
44
+ *Generated by [Diogenes](https://github.com/meaganewaller/diogenes)*
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Diogenes project configuration.
4
+ # Run `diogenes build --all` after changing source files.
5
+
6
+ Diogenes.configure do
7
+ name "My Project"
8
+ description "AI agent configuration for this project"
9
+
10
+ targets :claude_code, :cursor
11
+
12
+ artifacts_dir "docs/decisions"
13
+ end
@@ -0,0 +1,15 @@
1
+ # Hooks
2
+
3
+ Hooks define event-triggered behaviors that run automatically during agent sessions.
4
+
5
+ Add `.rb` files to this directory when you're ready. Example:
6
+
7
+ ```ruby
8
+ Diogenes.hook "run_tests_before_commit" do
9
+ trigger "PreToolUse"
10
+ description "Remind the agent to run tests before committing"
11
+ prompt <<~PROMPT
12
+ Before making any git commit, run the test suite and confirm it passes.
13
+ PROMPT
14
+ end
15
+ ```
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ Diogenes.rule "five_gates" do
4
+ description "The five Diogenes gates — default ruleset for AI feature decisions"
5
+
6
+ content <<~RULE
7
+ ## The Five Diogenes Gates
8
+
9
+ When evaluating or building AI features, apply these gates in order.
10
+ A gate failure is information, not a verdict — but it must be addressed
11
+ before shipping.
12
+
13
+ **Gate 1 — Failure Mode** (Least surprise at scale)
14
+ What happens when this feature is wrong? Recoverable failures pass.
15
+ Embarrassing failures pass with conditions. Catastrophic failures do not pass.
16
+
17
+ **Gate 2 — User Verifiable** (Trust requires verification)
18
+ Can the average user tell when the output is wrong? If the user lacks
19
+ domain expertise to evaluate AI output, the feature creates a confidence gap.
20
+
21
+ **Gate 3 — Human in the Loop** (Human-centered design, genuinely)
22
+ Is there a human with time, context, and authority to actually intervene?
23
+ Rubber-stamping is not a loop.
24
+
25
+ **Gate 4 — Observability** (Craftsmanship — you wouldn't ship blind)
26
+ Do you have monitoring to detect when this feature degrades in production?
27
+ Silent degradation is worse than no feature.
28
+
29
+ **Gate 5 — Right Tool** (Convention over configuration)
30
+ Is AI the right answer, or just the exciting one? Prefer the boring,
31
+ predictable software alternative when it serves the same need.
32
+ RULE
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example skill demonstrating the full Diogenes skill DSL.
4
+ # Copy this file, rename it, and customize the values for your project.
5
+
6
+ Diogenes.skill "evaluate_feature" do
7
+ command "/evaluate-feature"
8
+ description "Walk a proposed AI feature through the five Diogenes gates"
9
+
10
+ prompt <<~PROMPT
11
+ You are helping a Ruby developer evaluate whether a proposed AI feature
12
+ should be built. Walk them through each of the five Diogenes gates in order,
13
+ asking focused questions and surfacing the failure mode clearly if a gate fails.
14
+
15
+ The feature being evaluated: {{input}}
16
+
17
+ The five gates are:
18
+ 1. Failure Mode — What happens when this feature is wrong? Is the failure recoverable?
19
+ 2. User Verifiable — Can the average user tell when it's wrong?
20
+ 3. Human in the Loop — Is there a real human checking, with time and authority to intervene?
21
+ 4. Observability — Do you have monitoring to know when it's going wrong in production?
22
+ 5. Right Tool — Is AI the right answer, or just the exciting one?
23
+
24
+ For each gate:
25
+ - State the gate name and the Ruby principle it maps to
26
+ - Ask one focused question to determine pass or fail
27
+ - State the verdict clearly (PASS or FAIL)
28
+ - If FAIL: explain why and suggest what the right approach is instead
29
+
30
+ At the end, generate a structured decision record with a summary verdict:
31
+ PROCEED, REJECT, or PROCEED WITH CONDITIONS.
32
+ PROMPT
33
+ end
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.1.7"
2
+ ".": "0.1.8"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.8](https://github.com/meaganewaller/diogenes/compare/diogenes/v0.1.7...diogenes/v0.1.8) (2026-06-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **build:** emit agent config files from .diogenes/ sources :rocket: ([#15](https://github.com/meaganewaller/diogenes/issues/15)) ([d2727b6](https://github.com/meaganewaller/diogenes/commit/d2727b6fa51e0646e4042e24ad665bf740642d34))
9
+
3
10
  ## [0.1.7](https://github.com/meaganewaller/diogenes/compare/diogenes/v0.1.6...diogenes/v0.1.7) (2026-06-27)
4
11
 
5
12
 
data/CLAUDE.md CHANGED
@@ -1,146 +1,42 @@
1
- # Diogenes — Claude Instructions
1
+ # Agent Configuration
2
2
 
3
- > This file is generated by `diogenes build --target claude-code`.
4
- > Edit the source in `.diogenes/` and rebuild. Do not edit this file directly.
5
-
6
- ---
7
-
8
- ## What This Gem Is
9
-
10
- Diogenes is a Ruby gem with two surfaces:
11
-
12
- **CLI / Build Tool** — Takes a canonical `.diogenes/` source (skills, rules, hooks, artifacts defined in a Ruby DSL) and builds agent-specific configuration files for Claude Code, Cursor, Copilot, Codex, Gemini, and other agents. One source of truth, many targets.
13
-
14
- **Runtime Library** — A `Diogenes::Gated` module you include in Ruby classes that touch AI. It enforces a five-gate decision framework at class load time (loud in development, graceful in production) and provides RSpec/Minitest matchers so gate decisions live in the test suite.
15
-
16
- The gem is grounded in a decision framework for AI features derived from Ruby's core principles: least surprise, programmer happiness, and human-centered design.
17
-
18
- ---
19
-
20
- ## Codebase Map
21
-
22
- ```text
23
- lib/
24
- ├── diogenes.rb # entry point, autoloads, top-level configure block
25
- ├── diogenes/
26
- │ ├── cli/ # Thor-based CLI commands
27
- │ │ ├── init.rb # diogenes init
28
- │ │ ├── build.rb # diogenes build
29
- │ │ ├── evaluate.rb # diogenes evaluate (interactive)
30
- │ │ └── validate.rb # diogenes validate
31
- │ ├── dsl/ # source format DSL
32
- │ │ ├── skill.rb # Diogenes.skill { ... }
33
- │ │ ├── rule.rb # Diogenes.rule { ... }
34
- │ │ ├── hook.rb # Diogenes.hook { ... }
35
- │ │ └── artifact.rb # Diogenes.artifact { ... }
36
- │ ├── gates/ # one file per gate
37
- │ │ ├── base.rb # shared validation interface
38
- │ │ ├── failure_mode.rb # Gate 1
39
- │ │ ├── user_verifiable.rb # Gate 2
40
- │ │ ├── human_in_loop.rb # Gate 3
41
- │ │ ├── observability.rb # Gate 4
42
- │ │ └── right_tool.rb # Gate 5
43
- │ ├── targets/ # one file per agent target
44
- │ │ ├── base.rb # shared emit interface
45
- │ │ ├── claude_code.rb # .claude/ + CLAUDE.md
46
- │ │ ├── cursor.rb # .cursor/rules/
47
- │ │ ├── copilot.rb # .github/copilot-instructions.md
48
- │ │ ├── codex.rb # codex-instructions.md + tools.json
49
- │ │ └── gemini.rb # .gemini/instructions.md
50
- │ ├── runtime/ # the Gated module and GateResult
51
- │ │ ├── gated.rb # include Diogenes::Gated
52
- │ │ ├── gate_result.rb # structured pass/fail response
53
- │ │ └── environment.rb # development vs production behaviour
54
- │ └── rspec/ # RSpec matchers
55
- │ └── matchers.rb # have_gate, pass_gate, fail_gate
56
- spec/ # mirrors lib/
57
- exe/
58
- └── diogenes # CLI entry point
59
- .diogenes/ # this gem's own canonical source
60
- ```
61
-
62
- ---
63
-
64
- ## Key Conventions
65
-
66
- **Error messages are human.** When a gate fails or a configuration is invalid, the message should say what happened, why it matters, and what to do instead. Not just what went wrong — what to do about it.
67
-
68
- **Development is loud, production is graceful.** Gate failures raise in development (`Diogenes::GateFailed`), return a `GateResult` in production. Environment is detected via `Rails.env` if present, otherwise `ENV["DIOGENES_ENV"]`.
69
-
70
- **The DSL is the source of truth.** Nothing in `lib/` should have an opinion about what format a particular agent uses. Targets do the translation. Gates do the validation. The DSL holds the intent.
71
-
72
- **Agent-agnostic means agent-agnostic.** When working on a target, resist any temptation to leak target-specific concepts into the DSL or gates. A skill is a skill. A rule is a rule. The target decides what to do with them.
73
-
74
- ---
3
+ > Generated by `diogenes build --target claude-code`. Edit sources in `.diogenes/` and rebuild. Do not edit directly.
75
4
 
76
5
  ## Available Skills
77
6
 
78
- Use these commands in any Claude session while working on this gem:
7
+ ### /evaluate-feature
79
8
 
80
- ### `/evaluate-feature <description>`
81
-
82
- Walk a proposed gem feature through the five Diogenes gates. Yes, the gem evaluates itself. Produces a decision record you should commit alongside the PR.
83
-
84
- *Use when:* You're about to build something new and want to check it against the framework before writing code.
85
-
86
- ### `/new-target <agent-name>`
87
-
88
- Scaffold a new build target. Generates `lib/diogenes/targets/<name>.rb`, the corresponding spec file, and a stub entry in `docs/targets.md`.
89
-
90
- *Use when:* Adding support for a new agent.
91
-
92
- ### `/review-philosophy`
93
-
94
- Review the current working context (open files, recent changes) for alignment with Diogenes' principles. Flags anything that feels like scope creep, API surface bloat, or a violation of the "agent-agnostic source" rule.
95
-
96
- *Use when:* You've been in the weeds for a while and want a sanity check.
97
-
98
- ### `/generate-stories <feature description>`
99
-
100
- Draft user stories with acceptance criteria for a proposed feature, scoped to the demo or the full roadmap depending on what you specify.
101
-
102
- *Use when:* Scoping new work before opening a PR.
103
-
104
- ### `/gate-schema <gate-name> <option-name>`
105
-
106
- Help design the schema and validation logic for a new gate option. Asks targeted questions about what passes, what fails, and what the failure message should say.
107
-
108
- *Use when:* Adding new options to an existing gate.
109
-
110
- ---
9
+ Walk a proposed AI feature through the five Diogenes gates
111
10
 
112
11
  ## Active Rules
113
12
 
114
- These apply to every session working on this gem:
13
+ ### five_gates
115
14
 
116
- **Stay agent-agnostic.** The `.diogenes/` DSL and the gate framework should never reference Claude, Cursor, or any specific agent. Targets handle translation. If you find yourself writing agent-specific logic outside a target file, stop.
15
+ ## The Five Diogenes Gates
117
16
 
118
- **The gates are not a checklist to game.** When evaluating proposed features with `/evaluate-feature`, engage honestly with each gate. A gate failure is information, not an obstacle.
17
+ When evaluating or building AI features, apply these gates in order.
18
+ A gate failure is information, not a verdict — but it must be addressed
19
+ before shipping.
119
20
 
120
- **Prefer explicit Ruby over clever Ruby.** This gem will be read by contributors who are encountering it for the first time. Clarity beats elegance. The person debugging this at midnight should understand it without context.
21
+ **Gate 1 Failure Mode** (Least surprise at scale)
22
+ What happens when this feature is wrong? Recoverable failures pass.
23
+ Embarrassing failures pass with conditions. Catastrophic failures do not pass.
121
24
 
122
- **Gate failure messages are first-class.** When you write or modify a gate, the failure message is as important as the validation logic. Draft it first. It should sound like a thoughtful colleague pointing something out, not a compiler.
25
+ **Gate 2 User Verifiable** (Trust requires verification)
26
+ Can the average user tell when the output is wrong? If the user lacks
27
+ domain expertise to evaluate AI output, the feature creates a confidence gap.
123
28
 
124
- **Tests are not optional.** Every gate option, every target, every CLI command needs tests. If you're uncertain what to test, that uncertainty is a sign the API needs clarification, not that tests can be skipped.
29
+ **Gate 3 Human in the Loop** (Human-centered design, genuinely)
30
+ Is there a human with time, context, and authority to actually intervene?
31
+ Rubber-stamping is not a loop.
125
32
 
126
- ---
33
+ **Gate 4 — Observability** (Craftsmanship — you wouldn't ship blind)
34
+ Do you have monitoring to detect when this feature degrades in production?
35
+ Silent degradation is worse than no feature.
127
36
 
128
- ## Running the Gem Locally
37
+ **Gate 5 Right Tool** (Convention over configuration)
38
+ Is AI the right answer, or just the exciting one? Prefer the boring,
39
+ predictable software alternative when it serves the same need.
129
40
 
130
- ```bash
131
- bundle exec exe/diogenes --help
132
- bundle exec exe/diogenes init
133
- bundle exec exe/diogenes evaluate "some feature idea"
134
- bundle exec exe/diogenes build --all
135
- bundle exec rspec
136
- ```
137
41
 
138
42
  ---
139
-
140
- ## Rebuilding This File
141
-
142
- This file was generated from `.diogenes/`. To regenerate after source changes:
143
-
144
- ```bash
145
- bundle exec exe/diogenes build --target claude-code
146
- ```
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Diogenes
5
+ module Build
6
+ Sources = Struct.new(:skills, :rules, :hooks, :artifacts, keyword_init: true)
7
+ end
8
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "optparse"
5
+
6
+ module Diogenes
7
+ class Cli
8
+ class Build
9
+ DIOGENES_DIR = ".diogenes" #: String
10
+ SOURCE_SUBDIRS = %w[skills rules hooks].freeze #: Array[String]
11
+
12
+ #: (argv: Array[String], cwd: String, out: IO, err: IO, **untyped) -> Integer
13
+ def self.run(argv:, cwd:, out:, err:, **_opts)
14
+ new(argv:, cwd:, out:, err:).run
15
+ end
16
+
17
+ #: (argv: Array[String], cwd: String, out: IO, err: IO) -> void
18
+ def initialize(argv:, cwd:, out:, err:)
19
+ @argv = argv #: Array[String]
20
+ @cwd = cwd #: String
21
+ @out = out #: IO
22
+ @err = err #: IO
23
+ @build_all = false #: bool
24
+ @target_key = nil #: Symbol?
25
+ end
26
+
27
+ #: () -> Integer
28
+ def run
29
+ parse_options
30
+ return usage_error unless @build_all || @target_key
31
+
32
+ diogenes_dir = File.join(@cwd, DIOGENES_DIR)
33
+ unless Dir.exist?(diogenes_dir)
34
+ @err.puts "No #{DIOGENES_DIR}/ found. Run `diogenes init` first."
35
+ return 1
36
+ end
37
+
38
+ Diogenes.reset!
39
+ load_config(diogenes_dir)
40
+ load_sources(diogenes_dir)
41
+
42
+ target_keys = @target_key ? [@target_key] : Diogenes.configuration.targets
43
+
44
+ if target_keys.empty?
45
+ @err.puts "No targets configured. Add `targets :claude_code, :cursor` to #{DIOGENES_DIR}/diogenes.rb"
46
+ return 1
47
+ end
48
+
49
+ sources = Diogenes::Build::Sources.new(
50
+ skills: Diogenes.skills,
51
+ rules: Diogenes.rules,
52
+ hooks: Diogenes.hooks,
53
+ artifacts: Diogenes.artifacts
54
+ )
55
+
56
+ built = build_targets(target_keys, sources)
57
+ print_summary(built)
58
+ 0
59
+ rescue UserError => e
60
+ @err.puts e.message
61
+ 1
62
+ end
63
+
64
+ private
65
+
66
+ #: () -> void
67
+ def parse_options
68
+ OptionParser.new do |o|
69
+ o.on("--all") { @build_all = true }
70
+ o.on("--target TARGET") { |v| @target_key = v.to_sym }
71
+ end.parse!(@argv.dup)
72
+ end
73
+
74
+ #: () -> Integer
75
+ def usage_error
76
+ @err.puts "Usage: diogenes build --all"
77
+ @err.puts " diogenes build --target <target>"
78
+ 1
79
+ end
80
+
81
+ #: (String) -> void
82
+ def load_config(dir)
83
+ config = File.join(dir, "diogenes.rb")
84
+ load config if File.exist?(config)
85
+ end
86
+
87
+ #: (String) -> void
88
+ def load_sources(dir)
89
+ SOURCE_SUBDIRS.each do |subdir|
90
+ Dir.glob(File.join(dir, subdir, "**/*.rb")).sort.each { |f| load f }
91
+ end
92
+ end
93
+
94
+ #: (Array[Symbol], untyped) -> Array[Hash[Symbol, untyped]]
95
+ def build_targets(keys, sources)
96
+ keys.map do |key|
97
+ target = Targets.for(key, cwd: @cwd, out: @out)
98
+ files = target.build(sources:)
99
+ {key:, files:}
100
+ end
101
+ end
102
+
103
+ #: (Array[Hash[Symbol, untyped]]) -> void
104
+ def print_summary(built)
105
+ @out.puts
106
+ @out.puts "Build complete."
107
+ @out.puts
108
+ built.each do |b|
109
+ @out.puts " #{b[:key]}:"
110
+ b[:files].each { |f| @out.puts " wrote #{f}" }
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
data/lib/diogenes/cli.rb CHANGED
@@ -2,12 +2,14 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  require_relative "cli/init"
5
+ require_relative "cli/build"
5
6
  require_relative "cli/evaluate"
6
7
 
7
8
  module Diogenes
8
9
  class Cli
9
10
  COMMANDS = {
10
11
  "init" => Init,
12
+ "build" => Build,
11
13
  "evaluate" => Evaluate
12
14
  }.freeze #: Hash[String, Class]
13
15
 
@@ -22,6 +24,7 @@ module Diogenes
22
24
  @out = out #: IO
23
25
  @err = err #: IO
24
26
  @in = opts.fetch(:in, $stdin) #: IO
27
+ @cwd = opts.fetch(:cwd, Dir.pwd) #: String
25
28
  end
26
29
 
27
30
  #: () -> Integer
@@ -47,8 +50,8 @@ module Diogenes
47
50
 
48
51
  #: (Class, Array[String]) -> Integer
49
52
  def run_command(command_class, args)
50
- opts = {in: @in}
51
- command_class.run(argv: args, cwd: Dir.pwd, out: @out, err: @err, **opts)
53
+ opts = {in: @in, cwd: @cwd}
54
+ command_class.run(argv: args, cwd: @cwd, out: @out, err: @err, **opts)
52
55
  end
53
56
 
54
57
  #: () -> Integer
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ module Diogenes
5
+ class Configuration
6
+ DEFAULT_ARTIFACTS_DIR = "docs/decisions" #: String
7
+
8
+ #: () -> void
9
+ def initialize
10
+ @targets = [] #: Array[Symbol]
11
+ @artifacts_dir = DEFAULT_ARTIFACTS_DIR #: String
12
+ end
13
+
14
+ #: (?String?) -> String?
15
+ def name(value = nil)
16
+ @name = value unless value.nil?
17
+ @name
18
+ end
19
+
20
+ #: (?String?) -> String?
21
+ def description(value = nil)
22
+ @description = value unless value.nil?
23
+ @description
24
+ end
25
+
26
+ #: (*Symbol) -> Array[Symbol]
27
+ def targets(*values)
28
+ @targets = values.flatten unless values.empty?
29
+ @targets
30
+ end
31
+
32
+ #: (?String?) -> String
33
+ def artifacts_dir(value = nil)
34
+ @artifacts_dir = value unless value.nil?
35
+ @artifacts_dir
36
+ end
37
+ end
38
+ end