diogenes 0.1.5 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00c7fad965257b62e461fbd2ae43db54c10bb7cc6c93d454cb2563ae1c6eb6a8
4
- data.tar.gz: 69e47cf048ce0a2d31036ad803ebefcb0cf66bb6d6417ca517d2dc6cc52343d5
3
+ metadata.gz: a7437b70a7659349d8486c0f1e5c6cf7a6735b120e1710cb94d3dacf05ba85b8
4
+ data.tar.gz: ffeee3838b29625af1f2f07f55459fd96d1678cfa2e161160433d818c36826fb
5
5
  SHA512:
6
- metadata.gz: f3a0ad8ca1f41be1e4edfe1fe38552d954ecdf1d5d2dc6e417c713be956673c2ae67b0781fdb5720b693c9aab999b8a31c9b0eac55ccec5e16a3eb392c0decec
7
- data.tar.gz: 42b1551a1bb754c6252d9fa0d04cdc0894251ac76f38656c9c1ae003d5854d59e816f4de3823fca4f2056a97d6a7b8b41026e112be6fa1b3a8ffbb8ecdac2164
6
+ metadata.gz: 8c533c33b568af4710da44f2ca825710bfed3c6d7ccfa4956303183817b36aca7facb46b469e3081998ce0ca4b21a01f61e12dcaaace3f711db5d2feb4721faa
7
+ data.tar.gz: 10ab65d16346cd75fb17f1c6e9a1fbbae742ce12a62255eb5e2845a2d65840c58510f15a5da9df6c439c29f02a66b5aae00d7ae768180c7d800d0f5b271235d5
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.1.5"
2
+ ".": "0.1.6"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.6](https://github.com/meaganewaller/diogenes/compare/diogenes/v0.1.5...diogenes/v0.1.6) (2026-06-27)
4
+
5
+
6
+ ### Features
7
+
8
+ * **evaluate:** generate decision record artifact after gate evaluation :rocket: ([#11](https://github.com/meaganewaller/diogenes/issues/11)) ([14d9ac2](https://github.com/meaganewaller/diogenes/commit/14d9ac2a12d4c61378c442a833d878bdf62ebc0b))
9
+
3
10
  ## [0.1.5](https://github.com/meaganewaller/diogenes/compare/diogenes/v0.1.4...diogenes/v0.1.5) (2026-06-27)
4
11
 
5
12
 
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+ # rbs_inline: enabled
3
+
4
+ require "erb"
5
+ require "date"
6
+ require "fileutils"
7
+
8
+ module Diogenes
9
+ module Evaluation
10
+ class DecisionRecord
11
+ DEFAULT_OUTPUT_DIR = "docs/decisions" #: String
12
+ BUNDLED_TEMPLATE = File.expand_path("../templates/init/artifacts/decision_record.md.erb", __dir__).freeze #: String
13
+ PROJECT_TEMPLATE = ".diogenes/artifacts/decision_record.md.erb" #: String
14
+
15
+ #: (description: String, results: Array[untyped], evaluator: String, ?output_dir: String) -> void
16
+ def initialize(description:, results:, evaluator:, output_dir: DEFAULT_OUTPUT_DIR)
17
+ @description = description
18
+ @results = results
19
+ @evaluator = evaluator
20
+ @output_dir = output_dir
21
+ @alternative = "" #: String
22
+ @conditions = "" #: String
23
+ @notes = "" #: String
24
+ end
25
+
26
+ attr_writer :alternative #: String
27
+ attr_writer :conditions #: String
28
+ attr_writer :notes #: String
29
+
30
+ #: () -> String
31
+ def verdict
32
+ @verdict ||= @results.all?(&:passed?) ? "PROCEED" : "REJECT"
33
+ end
34
+
35
+ #: (String) -> void
36
+ attr_writer :verdict
37
+
38
+ #: () -> String
39
+ def filename
40
+ "#{slugify(@description)}_decision.md"
41
+ end
42
+
43
+ #: (cwd: String) -> String
44
+ def write(cwd:)
45
+ dir = File.join(cwd, @output_dir)
46
+ FileUtils.mkdir_p(dir)
47
+ path = File.join(dir, filename)
48
+ File.write(path, render(cwd:))
49
+ path
50
+ end
51
+
52
+ private
53
+
54
+ #: (cwd: String) -> String
55
+ def render(cwd:)
56
+ ctx = RenderContext.new(
57
+ feature_name: @description,
58
+ evaluator: @evaluator,
59
+ verdict: verdict,
60
+ results: @results,
61
+ alternative: @alternative,
62
+ conditions: @conditions,
63
+ notes: @notes
64
+ )
65
+ ERB.new(read_template(cwd:), trim_mode: "-").result(ctx.get_binding)
66
+ end
67
+
68
+ #: (cwd: String) -> String
69
+ def read_template(cwd:)
70
+ project = File.join(cwd, PROJECT_TEMPLATE)
71
+ File.read(File.exist?(project) ? project : BUNDLED_TEMPLATE)
72
+ end
73
+
74
+ #: (String) -> String
75
+ def slugify(text)
76
+ text.strip.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
77
+ end
78
+
79
+ class RenderContext
80
+ #: (feature_name: String, evaluator: String, verdict: String, results: Array[untyped], alternative: String, conditions: String, notes: String) -> void
81
+ def initialize(feature_name:, evaluator:, verdict:, results:, alternative:, conditions:, notes:)
82
+ @feature_name = feature_name
83
+ @evaluator = evaluator
84
+ @verdict = verdict
85
+ @results = results
86
+ @alternative = alternative
87
+ @conditions = conditions
88
+ @notes = notes
89
+ end
90
+
91
+ attr_reader :feature_name
92
+ def feature_slug = @feature_name.strip.downcase.gsub(/[^a-z0-9]+/, "_").gsub(/\A_+|_+\z/, "")
93
+ attr_reader :evaluator
94
+ attr_reader :verdict
95
+ attr_reader :alternative
96
+ attr_reader :conditions
97
+ attr_reader :notes
98
+
99
+ #: (Symbol) -> String
100
+ def gate_result(key)
101
+ r = @results.find { |res| res.gate.key == key }
102
+ return "not evaluated" unless r
103
+ r.passed? ? "PASS" : "FAIL"
104
+ end
105
+
106
+ #: (Symbol) -> String
107
+ def gate_reason(key)
108
+ r = @results.find { |res| res.gate.key == key }
109
+ return "not evaluated" unless r
110
+ r.passed? ? "Confirmed" : "Not confirmed"
111
+ end
112
+
113
+ def get_binding = binding
114
+ end
115
+ end
116
+ end
117
+ end
@@ -17,6 +17,7 @@ module Diogenes
17
17
  @out = out
18
18
  @err = err
19
19
  @in = opts.fetch(:in, $stdin)
20
+ @cwd = opts.fetch(:cwd, Dir.pwd) #: String
20
21
  @results = [] #: Array[untyped]
21
22
  end
22
23
 
@@ -82,11 +83,48 @@ module Diogenes
82
83
 
83
84
  #: () -> void
84
85
  def prompt_decision_record
85
- if ask_yes_no("Generate a decision record? (Y/n)")
86
- @out.puts " Decision record generation is coming in the next release."
86
+ return unless ask_yes_no("Generate a decision record?")
87
+
88
+ record = DecisionRecord.new(
89
+ description: @description,
90
+ results: @results,
91
+ evaluator: detect_evaluator
92
+ )
93
+
94
+ collect_failure_details(record) if @results.any?(&:failed?)
95
+
96
+ path = record.write(cwd: @cwd)
97
+ @out.puts " Decision record written to #{path}"
98
+ end
99
+
100
+ #: (DecisionRecord) -> void
101
+ def collect_failure_details(record)
102
+ @out.puts
103
+ @out.puts " One or more gates failed. How should this be recorded?"
104
+ @out.puts " [1] REJECT — this feature should not use AI"
105
+ @out.puts " [2] PROCEED WITH CONDITIONS — proceeding with documented mitigations"
106
+ @out.print " Your choice (default: 1): "
107
+ choice = @in.gets&.strip
108
+
109
+ if choice == "2"
110
+ record.verdict = "PROCEED WITH CONDITIONS"
111
+ @out.print " Describe the conditions or mitigations: "
112
+ record.conditions = @in.gets&.strip || ""
113
+ else
114
+ record.verdict = "REJECT"
115
+ @out.print " Describe the recommended software alternative: "
116
+ record.alternative = @in.gets&.strip || ""
87
117
  end
88
118
  end
89
119
 
120
+ #: () -> String
121
+ def detect_evaluator
122
+ name = `git config user.name 2>/dev/null`.strip
123
+ name.empty? ? ENV.fetch("USER", "Unknown") : name
124
+ rescue Errno::ENOENT
125
+ ENV.fetch("USER", "Unknown")
126
+ end
127
+
90
128
  #: (String) -> bool
91
129
  def ask_yes_no(question)
92
130
  @out.print " #{question} "
@@ -1,53 +1,44 @@
1
- # frozen_string_literal: true
1
+ # AI Feature Decision Record
2
2
 
3
- Diogenes.artifact "decision_record" do
4
- description "Decision record produced by gate evaluation"
5
- filename "<%= feature_slug %>_decision.md"
3
+ **Feature:** <%= feature_name %>
4
+ **Date:** <%= Date.today.strftime("%Y-%m-%d") %>
5
+ **Evaluator:** <%= evaluator %>
6
+ **Verdict:** <%= verdict %>
6
7
 
7
- template <<~TEMPLATE
8
- # AI Feature Decision Record
8
+ ---
9
9
 
10
- **Feature:** <%= feature_name %>
11
- **Date:** <%= Date.today.strftime("%Y-%m-%d") %>
12
- **Evaluator:** <%= evaluator %>
13
- **Verdict:** <%= verdict %>
10
+ ## Gate Results
14
11
 
15
- ---
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) %> |
16
19
 
17
- ## Gate Results
20
+ ---
18
21
 
19
- | Gate | Principle | Result | Reason |
20
- |------|-----------|--------|--------|
21
- | 1. Failure Mode | Least surprise at scale | <%= gate_result(:failure_mode) %> | <%= gate_reason(:failure_mode) %> |
22
- | 2. User Verifiable | Trust requires verification | <%= gate_result(:user_verifiable) %> | <%= gate_reason(:user_verifiable) %> |
23
- | 3. Human in Loop | Human-centered, genuinely | <%= gate_result(:human_in_loop) %> | <%= gate_reason(:human_in_loop) %> |
24
- | 4. Observability | Craftsmanship | <%= gate_result(:observability) %> | <%= gate_reason(:observability) %> |
25
- | 5. Right Tool | Convention over configuration | <%= gate_result(:right_tool) %> | <%= gate_reason(:right_tool) %> |
22
+ ## Verdict: <%= verdict %>
26
23
 
27
- ---
24
+ <% if verdict == "REJECT" || verdict == "PROCEED WITH CONDITIONS" -%>
25
+ ### Recommended Alternative or Mitigation
28
26
 
29
- ## Verdict: <%= verdict %>
27
+ <%= alternative.empty? ? "_No alternative or mitigation described._" : alternative %>
30
28
 
31
- <% if verdict == "REJECT" || verdict == "PROCEED WITH CONDITIONS" %>
32
- ## Recommended Alternative or Mitigation
29
+ <% end -%>
30
+ <% if verdict == "PROCEED WITH CONDITIONS" -%>
31
+ ### Conditions for Proceeding
33
32
 
34
- <%= alternative %>
35
- <% end %>
33
+ <%= conditions.empty? ? "_No conditions specified._" : conditions %>
36
34
 
37
- <% if verdict == "PROCEED" || verdict == "PROCEED WITH CONDITIONS" %>
38
- ## Conditions for Proceeding
35
+ <% end -%>
36
+ ---
39
37
 
40
- <%= conditions.empty? ? "None — all gates passed." : conditions %>
41
- <% end %>
38
+ ## Notes
42
39
 
43
- ---
40
+ <%= notes.empty? ? "_No additional notes._" : notes %>
44
41
 
45
- ## Notes
42
+ ---
46
43
 
47
- <%= notes.empty? ? "_No additional notes._" : notes %>
48
-
49
- ---
50
-
51
- *Generated by [Diogenes](https://github.com/meaganewaller/diogenes)*
52
- TEMPLATE
53
- end
44
+ *Generated by [Diogenes](https://github.com/meaganewaller/diogenes)*
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module Diogenes
5
- VERSION = "0.1.5" #: String
5
+ VERSION = "0.1.6" #: String
6
6
  end
@@ -0,0 +1,72 @@
1
+ # Generated from lib/diogenes/evaluation/decision_record.rb with RBS::Inline
2
+
3
+ module Diogenes
4
+ module Evaluation
5
+ class DecisionRecord
6
+ DEFAULT_OUTPUT_DIR: String
7
+
8
+ BUNDLED_TEMPLATE: String
9
+
10
+ PROJECT_TEMPLATE: String
11
+
12
+ # : (description: String, results: Array[untyped], evaluator: String, ?output_dir: String) -> void
13
+ def initialize: (description: String, results: Array[untyped], evaluator: String, ?output_dir: String) -> void
14
+
15
+ attr_writer alternative: String
16
+
17
+ attr_writer conditions: String
18
+
19
+ attr_writer notes: String
20
+
21
+ # : () -> String
22
+ def verdict: () -> String
23
+
24
+ # : (String) -> void
25
+ attr_writer verdict: untyped
26
+
27
+ # : () -> String
28
+ def filename: () -> String
29
+
30
+ # : (cwd: String) -> String
31
+ def write: (cwd: String) -> String
32
+
33
+ private
34
+
35
+ # : (cwd: String) -> String
36
+ def render: (cwd: String) -> String
37
+
38
+ # : (cwd: String) -> String
39
+ def read_template: (cwd: String) -> String
40
+
41
+ # : (String) -> String
42
+ def slugify: (String) -> String
43
+
44
+ class RenderContext
45
+ # : (feature_name: String, evaluator: String, verdict: String, results: Array[untyped], alternative: String, conditions: String, notes: String) -> void
46
+ def initialize: (feature_name: String, evaluator: String, verdict: String, results: Array[untyped], alternative: String, conditions: String, notes: String) -> void
47
+
48
+ attr_reader feature_name: untyped
49
+
50
+ def feature_slug: () -> untyped
51
+
52
+ attr_reader evaluator: untyped
53
+
54
+ attr_reader verdict: untyped
55
+
56
+ attr_reader alternative: untyped
57
+
58
+ attr_reader conditions: untyped
59
+
60
+ attr_reader notes: untyped
61
+
62
+ # : (Symbol) -> String
63
+ def gate_result: (Symbol) -> String
64
+
65
+ # : (Symbol) -> String
66
+ def gate_reason: (Symbol) -> String
67
+
68
+ def get_binding: () -> untyped
69
+ end
70
+ end
71
+ end
72
+ end
@@ -34,6 +34,12 @@ module Diogenes
34
34
  # : () -> void
35
35
  def prompt_decision_record: () -> void
36
36
 
37
+ # : (DecisionRecord) -> void
38
+ def collect_failure_details: (DecisionRecord) -> void
39
+
40
+ # : () -> String
41
+ def detect_evaluator: () -> String
42
+
37
43
  # : (String) -> bool
38
44
  def ask_yes_no: (String) -> bool
39
45
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: diogenes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Meagan Waller
@@ -61,6 +61,7 @@ files:
61
61
  - lib/diogenes/dsl/hook.rb
62
62
  - lib/diogenes/dsl/rule.rb
63
63
  - lib/diogenes/dsl/skill.rb
64
+ - lib/diogenes/evaluation/decision_record.rb
64
65
  - lib/diogenes/evaluation/gate.rb
65
66
  - lib/diogenes/evaluation/gates.rb
66
67
  - lib/diogenes/evaluation/session.rb
@@ -79,6 +80,7 @@ files:
79
80
  - sig/generated/diogenes/dsl/hook.rbs
80
81
  - sig/generated/diogenes/dsl/rule.rbs
81
82
  - sig/generated/diogenes/dsl/skill.rbs
83
+ - sig/generated/diogenes/evaluation/decision_record.rbs
82
84
  - sig/generated/diogenes/evaluation/gate.rbs
83
85
  - sig/generated/diogenes/evaluation/gates.rbs
84
86
  - sig/generated/diogenes/evaluation/session.rbs