harnex 0.7.9 → 0.7.10

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: 56d9111b2ab4f12716444ecef721331d9878a4485a4b305ecd4a1ad7546d4b77
4
- data.tar.gz: 3f727b473455245ba916cabe1202a6abc2d6c5a07f4ae257b04898a296d9149c
3
+ metadata.gz: 2af8afe53ccea3fb668e920809da3fdbcd523ee5eef0179bcdd84f026077cff1
4
+ data.tar.gz: f675d4a401f703bf52adc88ce352fec04d2090533c3249bd0ac05e6828d030f0
5
5
  SHA512:
6
- metadata.gz: a2a87ae57e9ddc34bb85805ea9e07f3523691cf979361f687f4c3c68534c0b9f7c9e276b83d8d28a91a15bc52913ef41e1e36b1993127c97c68c87aa75af5766
7
- data.tar.gz: 92084ab180df22e16316226adaaa8170b0ab71ceeda1e4de3f097873ed6ecb8e22f8bca7b06b3c78b0a1a0c36d8b18c4b13dcebb7126aecd4523034fead38399
6
+ metadata.gz: cda7d84fd1f80c9526529b03293623eb16f76a78872e5e29640896be76f79a448ab3bf8f4c92c74c56b8f88b814739b7fbd201a6c91ba749d22ecdc5cf8a720e
7
+ data.tar.gz: bc6704fc2a86a01e61c1a431fd96781bbaf178029679bc1b5c64be699f82c49bbf8b27d4f921c0d7547752eadc2dc61cffc1bc946b99d0cd5d79dd46a0de77af
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.7.10] - 2026-07-10 | 10:57 PM | IST
6
+
7
+ ### Added
8
+
9
+ - `harnex run --artifact-report PATH` / `--validation-report PATH` now expose a
10
+ worker-writable `harnex.artifact_report.v1` sidecar path via
11
+ `HARNEX_ARTIFACT_REPORT_PATH` and `HARNEX_VALIDATION_REPORT_PATH`, ingest the
12
+ report at dispatch finalization, and append compact top-level
13
+ `artifact_report`, `validation`, and `artifacts` blocks to the dispatch
14
+ summary row.
15
+
16
+ ### Changed
17
+
18
+ - Dispatch telemetry docs, README, and the packaged dispatch agents-guide now
19
+ document sidecar proof while keeping plain-text `koder/` artifacts canonical.
20
+
21
+ ### Fixed
22
+
23
+ - Missing, malformed, unsupported-schema, and oversized artifact reports now
24
+ fail soft with `artifact_report.ingest_status` warning telemetry instead of
25
+ crashing or changing the wrapped process exit code.
26
+
5
27
  ## [0.7.9] - 2026-07-10 | 10:33 PM | IST
6
28
 
7
29
  ### Added
data/README.md CHANGED
@@ -268,6 +268,22 @@ Dispatch briefs can declare soft budget metadata through `--meta`:
268
268
  harnex run pi --meta '{"read_budget_lines":2000,"output_ceiling_lines":800}' ...
269
269
  ```
270
270
 
271
+ Workers can also write a small machine-readable proof sidecar while keeping the
272
+ canonical explanation in plain-text `koder/` files:
273
+
274
+ ```bash
275
+ harnex run pi --id pi-i-52 \
276
+ --artifact-report .harnex/reports/pi-i-52.json \
277
+ --context 'Run validation, update koder/issues/52.md, and write JSON proof to $HARNEX_ARTIFACT_REPORT_PATH' \
278
+ --auto-stop
279
+ ```
280
+
281
+ The sidecar schema is `harnex.artifact_report.v1`; harnex exposes the path as
282
+ `HARNEX_ARTIFACT_REPORT_PATH` / `HARNEX_VALIDATION_REPORT_PATH`, then records a
283
+ compact `artifact_report`, `validation`, and `artifacts` summary in the dispatch
284
+ row. Missing or malformed reports are warning telemetry, not wrapped-process
285
+ crashes.
286
+
271
287
  Those declared values are copied into summary `meta`. Terminal summary
272
288
  `actual` records timing, exit classification, token usage when the adapter can
273
289
  capture it, adapter-reported `cost_usd` when reliably available, git deltas,
@@ -95,6 +95,22 @@ harnex run codex --cwd /tmp/leximaze_eval_run_001 \
95
95
  `--root DIR` only overrides harnex's root attribution; it does not change the
96
96
  child process cwd. Neither flag is a sandbox.
97
97
 
98
+ For queue closeout, ask workers to write a compact sidecar in addition to their
99
+ plain-text `koder/` artifact:
100
+
101
+ ```bash
102
+ harnex run pi --id pi-i-NN --tmux pi-i-NN \
103
+ --artifact-report .harnex/reports/pi-i-NN.json \
104
+ --context 'Update the canonical koder file and write harnex.artifact_report.v1 proof to $HARNEX_ARTIFACT_REPORT_PATH' \
105
+ --auto-stop
106
+ ```
107
+
108
+ The worker should keep the full explanation in `koder/` and put only compact
109
+ machine-readable proof in the sidecar: validation command/status, typed
110
+ artifact summaries (`finding`, `review`, `gate`, `blocker`, etc.), evidence,
111
+ confidence, and canonical refs. Harnex records missing/malformed sidecars as
112
+ warning telemetry instead of failing the wrapped process.
113
+
98
114
  Pi runs use structured RPC (`pi --mode rpc`). Pass Pi child flags after `--`
99
115
  (e.g. `harnex run pi --context "..." -- --model anthropic/claude-sonnet-4-5 --thinking high`).
100
116
 
@@ -0,0 +1,221 @@
1
+ require "digest"
2
+ require "json"
3
+
4
+ module Harnex
5
+ module ArtifactReport
6
+ SCHEMA = "harnex.artifact_report.v1"
7
+ MAX_BYTES = 256 * 1024
8
+ MAX_ARTIFACTS = 50
9
+ MAX_COMMANDS = 50
10
+ MAX_CANONICAL_ARTIFACTS = 50
11
+ MAX_EVIDENCE_ITEMS = 20
12
+ MAX_STRING_LENGTH = 2_000
13
+
14
+ module_function
15
+
16
+ def ingest(path)
17
+ report_path = File.expand_path(path.to_s)
18
+ return missing(report_path) unless File.file?(report_path)
19
+
20
+ bytes = File.size(report_path)
21
+ sha256 = file_sha256(report_path)
22
+ if bytes > MAX_BYTES
23
+ return warning(
24
+ report_path,
25
+ bytes: bytes,
26
+ sha256: sha256,
27
+ ingest_status: "oversized",
28
+ warning: "artifact report is #{bytes} bytes; max is #{MAX_BYTES} bytes"
29
+ )
30
+ end
31
+
32
+ parsed = JSON.parse(File.read(report_path, mode: "rb"))
33
+ unless parsed.is_a?(Hash)
34
+ return warning(
35
+ report_path,
36
+ bytes: bytes,
37
+ sha256: sha256,
38
+ ingest_status: "malformed",
39
+ warning: "artifact report must be a JSON object"
40
+ )
41
+ end
42
+
43
+ schema = parsed["schema"].to_s
44
+ unless schema == SCHEMA
45
+ return warning(
46
+ report_path,
47
+ bytes: bytes,
48
+ sha256: sha256,
49
+ ingest_status: "unsupported_schema",
50
+ schema: schema.empty? ? nil : bounded_string(schema),
51
+ warning: "unsupported artifact report schema #{schema.inspect}; expected #{SCHEMA}"
52
+ )
53
+ end
54
+
55
+ build_payload(report_path, bytes: bytes, sha256: sha256, report: parsed)
56
+ rescue JSON::ParserError => e
57
+ warning(
58
+ report_path,
59
+ bytes: safe_file_size(report_path),
60
+ sha256: safe_file_sha256(report_path),
61
+ ingest_status: "malformed",
62
+ warning: "malformed artifact report JSON: #{bounded_string(e.message)}"
63
+ )
64
+ rescue StandardError => e
65
+ warning(
66
+ report_path,
67
+ bytes: safe_file_size(report_path),
68
+ sha256: safe_file_sha256(report_path),
69
+ ingest_status: "error",
70
+ warning: "artifact report ingest failed: #{bounded_string(e.message)}"
71
+ )
72
+ end
73
+
74
+ def build_payload(path, bytes:, sha256:, report:)
75
+ artifacts = compact_artifacts(report["artifacts"])
76
+ validation = compact_validation(report["validation"])
77
+ payload = {
78
+ "artifact_report" => metadata(
79
+ path,
80
+ bytes: bytes,
81
+ sha256: sha256,
82
+ ingest_status: "ok",
83
+ schema: SCHEMA
84
+ ).merge(
85
+ "report_status" => bounded_string_or_nil(report["status"]),
86
+ "canonical_artifacts" => string_array(report["canonical_artifacts"], max_items: MAX_CANONICAL_ARTIFACTS),
87
+ "artifact_count" => artifacts.length
88
+ )
89
+ }
90
+ payload["validation"] = validation if validation
91
+ payload["artifacts"] = artifacts unless artifacts.empty?
92
+ payload
93
+ end
94
+
95
+ def missing(path)
96
+ warning(
97
+ path,
98
+ bytes: nil,
99
+ sha256: nil,
100
+ ingest_status: "missing",
101
+ warning: "artifact report not found"
102
+ )
103
+ end
104
+
105
+ def warning(path, bytes:, sha256:, ingest_status:, warning:, schema: nil)
106
+ report = metadata(path, bytes: bytes, sha256: sha256, ingest_status: ingest_status, schema: schema)
107
+ report["warning"] = bounded_string(warning)
108
+ { "artifact_report" => report }
109
+ end
110
+
111
+ def metadata(path, bytes:, sha256:, ingest_status:, schema: nil)
112
+ {
113
+ "path" => path.to_s,
114
+ "bytes" => bytes,
115
+ "sha256" => sha256,
116
+ "ingest_status" => ingest_status,
117
+ "schema" => schema
118
+ }
119
+ end
120
+
121
+ def compact_validation(value)
122
+ return nil unless value.is_a?(Hash)
123
+
124
+ payload = {}
125
+ payload["status"] = bounded_string_or_nil(value["status"])
126
+ payload["commands"] = compact_commands(value["commands"])
127
+ payload["final_reported"] = !!value["final_reported"] if value.key?("final_reported")
128
+ payload.delete_if { |_key, v| v.nil? || v == [] }
129
+ payload.empty? ? nil : payload
130
+ end
131
+
132
+ def compact_commands(value)
133
+ Array(value).first(MAX_COMMANDS).filter_map do |entry|
134
+ next unless entry.is_a?(Hash)
135
+
136
+ compact = {
137
+ "cmd" => bounded_string_or_nil(entry["cmd"]),
138
+ "exit_code" => integer_or_nil(entry["exit_code"]),
139
+ "status" => bounded_string_or_nil(entry["status"]),
140
+ "duration_s" => finite_float_or_nil(entry["duration_s"])
141
+ }
142
+ compact.delete_if { |_key, v| v.nil? }
143
+ compact.empty? ? nil : compact
144
+ end
145
+ end
146
+
147
+ def compact_artifacts(value)
148
+ Array(value).first(MAX_ARTIFACTS).filter_map do |entry|
149
+ next unless entry.is_a?(Hash)
150
+
151
+ compact = {
152
+ "type" => bounded_string_or_nil(entry["type"]),
153
+ "summary" => bounded_string_or_nil(entry["summary"]),
154
+ "evidence" => string_array(entry["evidence"], max_items: MAX_EVIDENCE_ITEMS),
155
+ "confidence" => finite_float_or_nil(entry["confidence"]),
156
+ "canonical_ref" => bounded_string_or_nil(entry["canonical_ref"])
157
+ }
158
+ compact.delete_if { |_key, v| v.nil? || v == [] }
159
+ compact.empty? ? nil : compact
160
+ end
161
+ end
162
+
163
+ def string_array(value, max_items:)
164
+ Array(value).first(max_items).filter_map do |item|
165
+ text = bounded_string_or_nil(item)
166
+ text unless text.nil? || text.empty?
167
+ end
168
+ end
169
+
170
+ def bounded_string_or_nil(value)
171
+ return nil if value.nil?
172
+
173
+ bounded_string(value)
174
+ end
175
+
176
+ def bounded_string(value)
177
+ text = value.to_s
178
+ return text if text.length <= MAX_STRING_LENGTH
179
+
180
+ text[0, MAX_STRING_LENGTH]
181
+ end
182
+
183
+ def integer_or_nil(value)
184
+ return nil if value.nil?
185
+
186
+ Integer(value)
187
+ rescue ArgumentError, TypeError
188
+ nil
189
+ end
190
+
191
+ def finite_float_or_nil(value)
192
+ return nil if value.nil?
193
+
194
+ number = Float(value)
195
+ number.finite? ? number : nil
196
+ rescue ArgumentError, TypeError
197
+ nil
198
+ end
199
+
200
+ def file_sha256(path)
201
+ digest = Digest::SHA256.new
202
+ File.open(path, "rb") do |file|
203
+ buffer = +""
204
+ digest.update(buffer) while file.read(16 * 1024, buffer)
205
+ end
206
+ digest.hexdigest
207
+ end
208
+
209
+ def safe_file_size(path)
210
+ File.size(path) if File.file?(path)
211
+ rescue StandardError
212
+ nil
213
+ end
214
+
215
+ def safe_file_sha256(path)
216
+ file_sha256(path) if File.file?(path)
217
+ rescue StandardError
218
+ nil
219
+ end
220
+ end
221
+ end
@@ -8,11 +8,11 @@ module Harnex
8
8
  KNOWN_FLAGS = %w[
9
9
  --id --description --detach --tmux --host --port --watch --watch-file
10
10
  --stall-after --max-resumes --preset --context --meta --summary-out
11
- --cwd --root --timeout --inbox-ttl --auto-stop --fast --legacy-pty --help
11
+ --artifact-report --validation-report --cwd --root --timeout --inbox-ttl --auto-stop --fast --legacy-pty --help
12
12
  ].freeze
13
13
  VALUE_FLAGS = %w[
14
14
  --id --description --host --port --watch --watch-file --stall-after
15
- --max-resumes --preset --context --meta --summary-out --cwd --root --timeout --inbox-ttl
15
+ --max-resumes --preset --context --meta --summary-out --artifact-report --validation-report --cwd --root --timeout --inbox-ttl
16
16
  ].freeze
17
17
 
18
18
  def self.usage(program_name = "harnex run")
@@ -37,6 +37,10 @@ module Harnex
37
37
  Default Codex runs force service_tier="flex".
38
38
  --meta JSON Attach parsed JSON metadata to the started event
39
39
  --summary-out PATH Append dispatch telemetry summary JSONL to PATH
40
+ --artifact-report PATH
41
+ Worker-written harnex.artifact_report.v1 JSON sidecar to ingest at exit
42
+ --validation-report PATH
43
+ Alias for --artifact-report; also exposed as HARNEX_VALIDATION_REPORT_PATH
40
44
  --cwd DIR Run the wrapped agent from DIR and use DIR as the session root
41
45
  --root DIR Override harnex session/root attribution without changing child cwd
42
46
  --timeout SECS Max seconds to wait for detached registration (default: #{DEFAULT_TIMEOUT})
@@ -61,6 +65,7 @@ module Harnex
61
65
  #{program_name} pi --id pi-i-42 --tmux pi-i-42 --context "Read /tmp/task-impl-42.md" --auto-stop
62
66
  #{program_name} pi --id pi-i-42 --watch --preset impl --context "Read /tmp/task-impl-42.md"
63
67
  #{program_name} codex --cwd /tmp/public-bundle --id eval-001 --context "Read README.md and write OUTPUT.md" --auto-stop
68
+ #{program_name} pi --id pi-i-52 --artifact-report .harnex/reports/pi-i-52.json --context "Write proof to $HARNEX_ARTIFACT_REPORT_PATH" --auto-stop
64
69
  #{program_name} claude --id cl-r-42 --tmux cl-r-42 --description "Review task 42"
65
70
 
66
71
  Gotchas:
@@ -91,6 +96,7 @@ module Harnex
91
96
  context: nil,
92
97
  meta: nil,
93
98
  summary_out: nil,
99
+ artifact_report: nil,
94
100
  cwd: nil,
95
101
  root: nil,
96
102
  auto_stop: false,
@@ -117,6 +123,7 @@ module Harnex
117
123
 
118
124
  repo_root = resolve_run_root(cli_name, child_args)
119
125
  @options[:summary_out] = resolve_summary_out(repo_root)
126
+ @options[:artifact_report] = resolve_artifact_report(repo_root)
120
127
  @options[:id] ||= Harnex.generate_id(repo_root)
121
128
  validate_unique_id!(repo_root)
122
129
  effective_child_args = apply_context(apply_codex_service_tier(cli_name, child_args))
@@ -178,6 +185,7 @@ module Harnex
178
185
  tmux_cmd << "--auto-stop" if @options[:auto_stop]
179
186
  tmux_cmd += ["--meta", JSON.generate(@options[:meta])] if @options[:meta]
180
187
  tmux_cmd += ["--summary-out", @options[:summary_out]] if @options[:summary_out]
188
+ tmux_cmd += ["--artifact-report", @options[:artifact_report]] if @options[:artifact_report]
181
189
  tmux_cmd += ["--cwd", @options[:cwd]] if @options[:cwd]
182
190
  tmux_cmd += ["--root", @options[:root]] if @options[:root]
183
191
  tmux_cmd += ["--inbox-ttl", @options[:inbox_ttl].to_s]
@@ -287,6 +295,7 @@ module Harnex
287
295
  description: @options[:description],
288
296
  meta: @options[:meta],
289
297
  summary_out: @options[:summary_out],
298
+ artifact_report_path: @options[:artifact_report],
290
299
  inbox_ttl: @options[:inbox_ttl],
291
300
  auto_stop: @options[:auto_stop],
292
301
  launch_cwd: history_cwd,
@@ -468,6 +477,13 @@ module Harnex
468
477
  @options[:summary_out] = required_option_value(arg, argv[index])
469
478
  when /\A--summary-out=(.+)\z/
470
479
  @options[:summary_out] = required_option_value("--summary-out", Regexp.last_match(1))
480
+ when "--artifact-report", "--validation-report"
481
+ index += 1
482
+ @options[:artifact_report] = required_option_value(arg, argv[index])
483
+ when /\A--artifact-report=(.+)\z/
484
+ @options[:artifact_report] = required_option_value("--artifact-report", Regexp.last_match(1))
485
+ when /\A--validation-report=(.+)\z/
486
+ @options[:artifact_report] = required_option_value("--validation-report", Regexp.last_match(1))
471
487
  when "--cwd"
472
488
  index += 1
473
489
  @options[:cwd] = expand_existing_directory(required_option_value(arg, argv[index]), option_name: arg)
@@ -546,7 +562,7 @@ module Harnex
546
562
  nil
547
563
  when *VALUE_FLAGS
548
564
  index += 1
549
- when /\A--(?:id|description|host|port|watch|watch-file|stall-after|max-resumes|context|meta|summary-out|cwd|root|timeout|inbox-ttl)=/
565
+ when /\A--(?:id|description|host|port|watch|watch-file|stall-after|max-resumes|context|meta|summary-out|artifact-report|validation-report|cwd|root|timeout|inbox-ttl)=/
550
566
  nil
551
567
  when /\A--preset=/
552
568
  nil
@@ -565,7 +581,7 @@ module Harnex
565
581
  arg.start_with?(
566
582
  "--id=", "--description=", "--tmux=", "--host=", "--port=", "--watch=", "--watch-file=",
567
583
  "--stall-after=", "--max-resumes=", "--preset=", "--context=", "--meta=", "--summary-out=",
568
- "--cwd=", "--root=", "--timeout=", "--inbox-ttl="
584
+ "--artifact-report=", "--validation-report=", "--cwd=", "--root=", "--timeout=", "--inbox-ttl="
569
585
  )
570
586
  end
571
587
 
@@ -645,6 +661,15 @@ module Harnex
645
661
  File.expand_path(configured, repo_root)
646
662
  end
647
663
 
664
+ def resolve_artifact_report(repo_root)
665
+ configured = @options[:artifact_report]
666
+ return nil if configured.nil?
667
+
668
+ path = File.expand_path(configured, repo_root)
669
+ FileUtils.mkdir_p(File.dirname(path))
670
+ path
671
+ end
672
+
648
673
  def default_inbox_ttl
649
674
  value = ENV["HARNEX_INBOX_TTL"]
650
675
  return Inbox::DEFAULT_TTL.to_f if value.nil? || value.strip.empty?
@@ -59,10 +59,10 @@ module Harnex
59
59
  end
60
60
 
61
61
  attr_reader :repo_root, :launch_cwd, :child_cwd, :host, :port, :session_id, :token, :command, :pid, :id, :adapter, :watch,
62
- :inbox, :description, :meta, :summary_out, :output_log_path, :events_log_path,
62
+ :inbox, :description, :meta, :summary_out, :artifact_report_path, :output_log_path, :events_log_path,
63
63
  :started_at, :ended_at, :exit_code, :term_signal
64
64
 
65
- def initialize(adapter:, command:, repo_root:, host:, port: nil, id: DEFAULT_ID, watch: nil, description: nil, meta: nil, summary_out: nil, inbox_ttl: Inbox::DEFAULT_TTL, auto_stop: false, launch_cwd: nil, child_cwd: nil)
65
+ def initialize(adapter:, command:, repo_root:, host:, port: nil, id: DEFAULT_ID, watch: nil, description: nil, meta: nil, summary_out: nil, artifact_report_path: nil, inbox_ttl: Inbox::DEFAULT_TTL, auto_stop: false, launch_cwd: nil, child_cwd: nil)
66
66
  @adapter = adapter
67
67
  @command = command
68
68
  @repo_root = repo_root
@@ -76,6 +76,9 @@ module Harnex
76
76
  @meta = meta
77
77
  @summary_out = summary_out.to_s.strip
78
78
  @summary_out = nil if @summary_out.empty?
79
+ @artifact_report_path = artifact_report_path.to_s.strip
80
+ @artifact_report_path = nil if @artifact_report_path.empty?
81
+ @artifact_report_path = File.expand_path(@artifact_report_path, repo_root) if @artifact_report_path
79
82
  @registry_path = Harnex.registry_path(repo_root, @id)
80
83
  @output_log_path = Harnex.output_log_path(repo_root, @id)
81
84
  @events_log_path = Harnex.events_log_path(repo_root, @id)
@@ -772,6 +775,11 @@ module Harnex
772
775
  "HARNEX_SESSION_REPO_ROOT" => repo_root
773
776
  }
774
777
  env["HARNEX_DESCRIPTION"] = description if description
778
+ if artifact_report_path
779
+ env["HARNEX_ARTIFACT_REPORT_PATH"] = artifact_report_path
780
+ env["HARNEX_VALIDATION_REPORT_PATH"] = artifact_report_path
781
+ env["HARNEX_ARTIFACT_REPORT_SCHEMA"] = Harnex::ArtifactReport::SCHEMA
782
+ end
775
783
  env["HARNEX_SPAWNER_PANE"] = ENV["TMUX_PANE"] if ENV["TMUX_PANE"]
776
784
  env
777
785
  end
@@ -1157,11 +1165,13 @@ module Harnex
1157
1165
  end
1158
1166
 
1159
1167
  def build_summary_record
1160
- {
1168
+ record = {
1161
1169
  meta: build_summary_meta,
1162
1170
  predicted: summary_predicted_payload,
1163
1171
  actual: build_summary_actual
1164
1172
  }
1173
+ record.merge!(artifact_report_summary) if artifact_report_path
1174
+ record
1165
1175
  end
1166
1176
 
1167
1177
  def build_summary_meta
@@ -1297,6 +1307,21 @@ module Harnex
1297
1307
  predicted.is_a?(Hash) ? predicted : {}
1298
1308
  end
1299
1309
 
1310
+ def artifact_report_summary
1311
+ Harnex::ArtifactReport.ingest(artifact_report_path)
1312
+ rescue StandardError => e
1313
+ {
1314
+ "artifact_report" => {
1315
+ "path" => artifact_report_path,
1316
+ "bytes" => nil,
1317
+ "sha256" => nil,
1318
+ "ingest_status" => "error",
1319
+ "schema" => nil,
1320
+ "warning" => "artifact report ingest failed: #{e.message}"
1321
+ }
1322
+ }
1323
+ end
1324
+
1300
1325
  def meta_hash
1301
1326
  meta.is_a?(Hash) ? meta : {}
1302
1327
  end
@@ -1,4 +1,4 @@
1
1
  module Harnex
2
- VERSION = "0.7.9"
2
+ VERSION = "0.7.10"
3
3
  RELEASE_DATE = "2026-07-10"
4
4
  end
data/lib/harnex.rb CHANGED
@@ -6,6 +6,7 @@ require_relative "harnex/version"
6
6
  require_relative "harnex/core"
7
7
  require_relative "harnex/dispatch_history"
8
8
  require_relative "harnex/terminal_status"
9
+ require_relative "harnex/artifact_report"
9
10
  require_relative "harnex/watcher"
10
11
  require_relative "harnex/adapters"
11
12
  require_relative "harnex/runtime/session_state"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harnex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9
4
+ version: 0.7.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jikku Jose
@@ -40,6 +40,7 @@ files:
40
40
  - lib/harnex/adapters/generic.rb
41
41
  - lib/harnex/adapters/opencode.rb
42
42
  - lib/harnex/adapters/pi.rb
43
+ - lib/harnex/artifact_report.rb
43
44
  - lib/harnex/cli.rb
44
45
  - lib/harnex/codex/app_server/client.rb
45
46
  - lib/harnex/commands/agents_guide.rb