predictability-engine 0.8.6 → 0.11.5

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: 7ffeb4987ed78b4e7797934377c1508ca77b02b6d4f0e78019c1626d31be2fc5
4
- data.tar.gz: 5397ee1f50a7155cb715968d51b00cae1ac9d0fa8c3b0c500c9e844d092aad4a
3
+ metadata.gz: f44e183717ab04fd8fe35647721a39741d26fc337d360d1792407814bf6298ef
4
+ data.tar.gz: 0df772dc4b55abc978aa1f9d7ba8994d375683f0fc899bbcb8c18c1af1fd9ecc
5
5
  SHA512:
6
- metadata.gz: 482498aaa15ff8d4342260de4e369861bed6e8298a793b69fb0cddf33eab70ba55f24f07b6a8097440461a20496b5fbe29d39302b4dd9daf609c25422537e99f
7
- data.tar.gz: ed70b524ef1dd66466c7e340f669bd8ceeef975ceb1c72670b8aadde83ccc92a561374d6029a94dae5521ab780110d7fca0243d5737b01145ec69f0b6f46993c
6
+ metadata.gz: c8adb03e9f72ad485039b7957616ceb8dc2fb533d7b5ff0b72d611028c2cf224485299ec56f14d9a390ef8c35ff91d2be02b1b0aaad0b931643c7e3a79f354bd
7
+ data.tar.gz: 55ad22940cef1ffcad66759ee9f345fcc397db8dc2f24a71e1217c2a7ce12c7a5642ebd1ad514dd1bec9c8ffc5b54f3ae4d4b465f95aec2e0d1fc523fc5a7f3f
@@ -4,12 +4,13 @@
4
4
  # When running from the source tree (not an installed gem), point Bundler
5
5
  # at this gem's Gemfile so the binary works from any CWD.
6
6
  gemfile = File.expand_path('../Gemfile', __dir__)
7
- ENV['BUNDLE_GEMFILE'] ||= gemfile if File.exist?(gemfile)
8
-
9
- begin
10
- require 'bundler/setup'
11
- rescue LoadError
12
- # Bundler not available, hope for the best with system gems
7
+ if File.exist?(gemfile)
8
+ ENV['BUNDLE_GEMFILE'] ||= gemfile
9
+ begin
10
+ require 'bundler/setup'
11
+ rescue LoadError
12
+ # Bundler not available, hope for the best with system gems
13
+ end
13
14
  end
14
15
 
15
16
  $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
data/bin/setup CHANGED
@@ -1,31 +1,41 @@
1
1
  #!/usr/bin/env bash
2
- # bin/setup — ensures Ruby, then bootstraps Bundler and delegates to the PE CLI.
2
+ # bin/setup — ensures Ruby + Node.js, then bootstraps Bundler and delegates to the PE CLI.
3
+ #
4
+ # Works on macOS, Linux, and WSL with zero prerequisites:
5
+ # - If mise/asdf/rbenv/rvm is already installed, uses it.
6
+ # - Otherwise, installs mise automatically (brew on macOS, curl on Linux/WSL).
7
+ # mise reads .ruby-version AND .tool-versions, so Ruby + Node.js land in one shot.
8
+ #
9
+ # For Windows native, use: bin\setup.bat (invokes bin\setup.ps1 via PowerShell)
3
10
  set -euo pipefail
4
11
  cd "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
12
 
6
13
  # ── Ruby version guard ────────────────────────────────────────────────────────
7
14
  REQUIRED_RUBY_MAJOR=${REQUIRED_RUBY_MAJOR:-4}
8
15
 
9
- ensure_ruby() {
10
- if command -v ruby &>/dev/null; then
11
- major=$(RUBYOPT= ruby -e 'puts RUBY_VERSION.split(".").first')
12
- [ "$major" -ge "$REQUIRED_RUBY_MAJOR" ] && return 0
13
- echo "==> Ruby ${major}.x found but Ruby >= ${REQUIRED_RUBY_MAJOR}.0 is required."
16
+ install_mise() {
17
+ echo "==> Installing mise (manages Ruby + Node.js from .tool-versions)..."
18
+ if [[ "$(uname)" == "Darwin" ]] && command -v brew &>/dev/null; then
19
+ brew install mise
14
20
  else
15
- echo "==> Ruby not found. Ruby >= ${REQUIRED_RUBY_MAJOR}.0 is required."
21
+ curl -fsSL https://mise.run | sh || true
22
+ export PATH="$HOME/.local/bin:$PATH"
16
23
  fi
17
-
18
- echo "==> Trying version managers..."
19
- if command -v mise &>/dev/null; then mise install && return 0; fi
20
- if command -v asdf &>/dev/null; then asdf install && return 0; fi
21
- if command -v rbenv &>/dev/null; then rbenv install --skip-existing && return 0; fi
22
- if command -v rvm &>/dev/null; then
23
- rvm install "$(cat .ruby-version)" && rvm use "$(cat .ruby-version)" && return 0
24
+ if command -v mise &>/dev/null; then
25
+ eval "$(mise activate bash)" 2>/dev/null || true
24
26
  fi
27
+ }
28
+
29
+ _ruby_version_file_major() {
30
+ sed 's/ruby-//' .ruby-version 2>/dev/null | cut -d. -f1 || echo 0
31
+ }
25
32
 
26
- ruby_version=$(sed 's/ruby-//' .ruby-version)
33
+ _setup_error() {
34
+ local ruby_version
35
+ ruby_version=$(sed 's/ruby-//' .ruby-version 2>/dev/null || echo "${REQUIRED_RUBY_MAJOR}.x")
27
36
  echo ""
28
- echo "ERROR: No Ruby version manager found. Install Ruby ${ruby_version} manually:"
37
+ echo "ERROR: Could not auto-install Ruby ${ruby_version}."
38
+ echo " Install Ruby ${ruby_version} manually:"
29
39
  echo ""
30
40
  echo " macOS: brew install rbenv && rbenv install ${ruby_version}"
31
41
  echo " Linux: curl -fsSL https://mise.run | sh && mise install"
@@ -39,6 +49,35 @@ ensure_ruby() {
39
49
  exit 1
40
50
  }
41
51
 
52
+ ensure_ruby() {
53
+ if command -v ruby &>/dev/null; then
54
+ major=$(RUBYOPT= ruby -e 'puts RUBY_VERSION.split(".").first')
55
+ [ "$major" -ge "$REQUIRED_RUBY_MAJOR" ] && return 0
56
+ echo "==> Ruby ${major}.x found but Ruby >= ${REQUIRED_RUBY_MAJOR}.0 is required."
57
+ else
58
+ echo "==> Ruby not found. Ruby >= ${REQUIRED_RUBY_MAJOR}.0 is required."
59
+ fi
60
+
61
+ echo "==> Trying version managers..."
62
+ if command -v mise &>/dev/null; then mise install && return 0; fi
63
+ if command -v asdf &>/dev/null; then asdf install && return 0; fi
64
+ if command -v rbenv &>/dev/null; then rbenv install --skip-existing && return 0; fi
65
+ if command -v rvm &>/dev/null; then
66
+ rvm install "$(cat .ruby-version)" && rvm use "$(cat .ruby-version)" && return 0
67
+ fi
68
+
69
+ # Only attempt mise auto-install when .ruby-version can satisfy the requirement;
70
+ # skip immediately to the error when it cannot (e.g. REQUIRED_RUBY_MAJOR=99).
71
+ if [ "$(_ruby_version_file_major)" -ge "$REQUIRED_RUBY_MAJOR" ] 2>/dev/null; then
72
+ install_mise
73
+ if command -v mise &>/dev/null && mise install; then
74
+ return 0
75
+ fi
76
+ fi
77
+
78
+ _setup_error
79
+ }
80
+
42
81
  ensure_ruby
43
82
  # ─────────────────────────────────────────────────────────────────────────────
44
83
 
data/bin/setup.bat ADDED
@@ -0,0 +1,2 @@
1
+ @ECHO OFF
2
+ powershell -ExecutionPolicy Bypass -File "%~dp0setup.ps1" %*
data/bin/setup.ps1 ADDED
@@ -0,0 +1,94 @@
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Bootstrap Ruby and the Predictability Engine on Windows.
5
+ .DESCRIPTION
6
+ Installs Ruby 4.x + MSYS2 DevKit via winget (if Ruby >= 4 is not already present),
7
+ then runs gem install bundler, bundle install, and predictability-engine setup
8
+ (which handles Node.js, Playwright, and Chromium).
9
+
10
+ Invoke via the thin CMD wrapper: bin\setup.bat
11
+ Or directly from PowerShell: powershell -ExecutionPolicy Bypass -File bin\setup.ps1
12
+ #>
13
+
14
+ Set-StrictMode -Version Latest
15
+ $ErrorActionPreference = 'Stop'
16
+
17
+ $RequiredRubyMajor = 4
18
+
19
+ # Change to repo root (one level above bin/)
20
+ $RepoRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
21
+ Set-Location $RepoRoot
22
+
23
+ function Get-RubyMajorVersion {
24
+ try {
25
+ $out = & ruby -e 'puts RUBY_VERSION.split(".").first' 2>$null
26
+ $val = $out.Trim()
27
+ if ($val -match '^\d+$') { return [int]$val }
28
+ } catch { }
29
+ return $null
30
+ }
31
+
32
+ function Refresh-Path {
33
+ $machine = [System.Environment]::GetEnvironmentVariable('PATH', 'Machine')
34
+ $user = [System.Environment]::GetEnvironmentVariable('PATH', 'User')
35
+ $env:PATH = "$machine;$user"
36
+ }
37
+
38
+ function Install-Ruby {
39
+ $rubyVersion = (Get-Content .ruby-version).Trim() -replace '^ruby-', ''
40
+
41
+ if (Get-Command winget -ErrorAction SilentlyContinue) {
42
+ Write-Host "==> Installing Ruby+Devkit 4.x via winget..."
43
+ winget install --id RubyInstallerTeam.RubyWithDevKit.4 --source winget --silent --accept-package-agreements --accept-source-agreements
44
+ Refresh-Path
45
+ } else {
46
+ Write-Host @"
47
+
48
+ ERROR: winget not found. Install Ruby $rubyVersion manually:
49
+
50
+ Option 1 (Recommended):
51
+ Download Ruby+Devkit $rubyVersion-x64 from https://rubyinstaller.org/downloads/
52
+ Run the installer and check "Add Ruby to PATH".
53
+
54
+ Option 2:
55
+ Install winget (App Installer) from the Microsoft Store, then re-run:
56
+ bin\setup.bat
57
+
58
+ After installing Ruby, re-run: bin\setup.bat
59
+ "@
60
+ exit 1
61
+ }
62
+ }
63
+
64
+ # ── Main ───────────────────────────────────────────────────────────────────────
65
+ $major = Get-RubyMajorVersion
66
+ if ($null -eq $major) {
67
+ Write-Host "==> Ruby not found."
68
+ Install-Ruby
69
+ } elseif ($major -lt $RequiredRubyMajor) {
70
+ Write-Host "==> Ruby $major.x found but Ruby >= $RequiredRubyMajor.0 is required."
71
+ Install-Ruby
72
+ } else {
73
+ Write-Host "==> Ruby $major.x found."
74
+ }
75
+
76
+ # Verify Ruby is reachable after install (winget may require a new terminal on first install)
77
+ $major = Get-RubyMajorVersion
78
+ if ($null -eq $major -or $major -lt $RequiredRubyMajor) {
79
+ Write-Host @"
80
+
81
+ ERROR: Ruby is not in PATH after installation.
82
+ Close this terminal, open a new one, and re-run: bin\setup.bat
83
+ "@
84
+ exit 1
85
+ }
86
+
87
+ Write-Host "==> Installing Bundler..."
88
+ & gem install bundler --conservative
89
+
90
+ Write-Host "==> Installing Ruby gems..."
91
+ & bundle install --jobs 4 --retry 3
92
+
93
+ Write-Host "==> Running Predictability Engine setup (Node.js, Playwright, Chromium)..."
94
+ & bundle exec predictability-engine setup
@@ -104,21 +104,6 @@ module PredictabilityEngine
104
104
  run_and_print_report(source, :html, output: output)
105
105
  end
106
106
 
107
- desc 'landscape SOURCE [OUTPUT]', 'Alias for html_all'
108
- def landscape(source, output = nil)
109
- run_and_print_report(source, :landscape, output: output)
110
- end
111
-
112
- desc 'dashboard SOURCE [OUTPUT]', 'Alias for landscape'
113
- def dashboard(source, output = nil)
114
- landscape(source, output)
115
- end
116
-
117
- desc 'all_html SOURCE [OUTPUT]', 'Alias for html_all'
118
- def all_html(source, output = nil)
119
- html_all(source, output)
120
- end
121
-
122
107
  desc 'pdf SOURCE [OUTPUT]', 'Generate a PDF report'
123
108
  def pdf(source, output = nil)
124
109
  run_and_print_report(source, :pdf, output: output)
@@ -134,21 +119,11 @@ module PredictabilityEngine
134
119
  run_and_print_report(source, :markdown, output: output)
135
120
  end
136
121
 
137
- desc 'md SOURCE [OUTPUT]', 'Alias for markdown'
138
- def md(source, output = nil)
139
- markdown(source, output)
140
- end
141
-
142
122
  desc 'confluence SOURCE [OUTPUT]', 'Generate a Confluence markup report'
143
123
  def confluence(source, output = nil)
144
124
  run_and_print_report(source, :confluence, output: output)
145
125
  end
146
126
 
147
- desc 'conf SOURCE [OUTPUT]', 'Alias for confluence'
148
- def conf(source, output = nil)
149
- confluence(source, output)
150
- end
151
-
152
127
  desc 'png SOURCE [OUTPUT]', 'Generate a PNG report'
153
128
  def png(source, output = nil)
154
129
  run_and_print_report(source, :png, output: output)
@@ -192,41 +167,29 @@ module PredictabilityEngine
192
167
  end
193
168
  end
194
169
 
195
- class Cli < Thor
170
+ class Jira < Thor
196
171
  include CliBase
197
172
  include JiraConfigPrompter
198
173
 
199
- desc 'viz SUBCOMMAND ...ARGS', 'Visualization commands'
200
- subcommand 'viz', Viz
201
- desc 'summary SOURCE', 'Load data from SOURCE and show flow metrics summary'
202
- method_option :color, type: :boolean, default: true, desc: 'Enable/disable color output'
203
- def summary(source)
204
- items = PredictabilityEngine.load_items(source)
205
- PredictabilityEngine.logger.info { SummaryVisualizer.metrics_terminal(items, color: options[:color]) }
206
- end
207
-
208
- desc 'report SOURCE FORMAT [OUTPUT]', 'Generate a full report in various formats (terminal, html, pdf, md, conf)'
209
- method_option :color, type: :boolean, default: true, desc: 'Enable/disable color output'
210
- method_option :clean, type: :boolean, default: true, desc: 'Clean the report directory before generation'
211
- def report(input_source, format = 'terminal', output = nil)
212
- if format.to_sym != :terminal && output.nil? && options[:clean]
213
- ReportGenerator.clean_report_dir(input_source, **options)
214
- end
215
- PredictabilityEngine.run_and_print_report(input_source, format, options, output: output)
216
- end
217
-
218
- desc 'batch SOURCE', 'Run all report formats for the given SOURCE'
219
- method_option :color, type: :boolean, default: true, desc: 'Enable/disable color output'
220
- def batch(source)
221
- Viz.new([], options).all_formats(source)
222
- end
223
-
224
- desc 'setup', 'Install/update all dependencies (Ruby gems + Node.js + Playwright + Chromium)'
225
- def setup
226
- SetupManager.new.run
174
+ desc 'config PROFILE', 'Generate/Update Jira credentials in ~/.config/jira/jira_credentials.yml'
175
+ method_option :auth_mode, aliases: '-a', default: 'basic',
176
+ desc: 'Auth mode: basic | bearer | cookie | mfa_api | mfa_browser'
177
+ def config(profile)
178
+ site = ask('Jira site (e.g., https://your-domain.atlassian.net):')
179
+ context_path = ask('Context path, if any (e.g., /jira — leave blank for Atlassian Cloud):')
180
+ mode = options[:auth_mode]
181
+ profile_data = build_profile_data(site, context_path, mode)
182
+ path = Config.jira_credentials_file
183
+ FileUtils.mkdir_p(File.dirname(path))
184
+ cfg = File.exist?(path) ? Config.load_yaml_file(path) : {}
185
+ cfg ||= {}
186
+ cfg['profiles'] ||= {}
187
+ cfg['profiles'][profile] = profile_data
188
+ File.write(path, cfg.to_yaml)
189
+ PredictabilityEngine.logger.info { "Jira credentials for profile '#{profile}' saved to #{path}" }
227
190
  end
228
191
 
229
- desc 'init FILENAME', 'Create a template YAML file for JIRA source'
192
+ desc 'init FILENAME', 'Create a template YAML file for Jira source'
230
193
  def init(filename)
231
194
  filename += '.yml' unless filename.end_with?('.yml', '.yaml')
232
195
  content = <<~YAML
@@ -241,47 +204,25 @@ module PredictabilityEngine
241
204
  PredictabilityEngine.logger.info { "Template created at #{filename}" }
242
205
  end
243
206
 
244
- desc 'jira_config PROFILE', 'Generate/Update JIRA credentials in ~/.config/jira/jira_credentials.yml'
245
- method_option :auth_mode, aliases: '-a', default: 'basic',
246
- desc: 'Auth mode: basic | bearer | cookie | mfa_api | mfa_browser'
247
- def jira_config(profile)
248
- site = ask('Jira site (e.g., https://your-domain.atlassian.net):')
249
- context_path = ask('Context path, if any (e.g., /jira — leave blank for Atlassian Cloud):')
250
- mode = options[:auth_mode]
251
-
252
- profile_data = build_profile_data(site, context_path, mode)
253
-
254
- path = Config.jira_credentials_file
255
- FileUtils.mkdir_p(File.dirname(path))
256
-
257
- config = File.exist?(path) ? Config.load_yaml_file(path) : {}
258
- config ||= {}
259
- config['profiles'] ||= {}
260
- config['profiles'][profile] = profile_data
261
-
262
- File.write(path, config.to_yaml)
263
- PredictabilityEngine.logger.info { "Jira credentials for profile '#{profile}' saved to #{path}" }
264
- end
265
-
266
- desc 'jira_workflow PROFILE [OUTPUT]',
207
+ desc 'workflow PROFILE [OUTPUT]',
267
208
  'Extract Jira workflow statuses for PROFILE into an editable YAML mapping ' \
268
209
  '(default: ~/.config/jira/<profile>.workflow.yml). Re-running refreshes the ' \
269
210
  'snapshot while preserving any roles you already set.'
270
- def jira_workflow(profile, output = nil)
211
+ def workflow(profile, output = nil)
271
212
  path = output || JiraWorkflow.default_path(profile)
272
213
  fresh = JiraWorkflow.extract(profile)
273
- workflow = File.exist?(path) ? JiraWorkflow.load(path).refresh(fresh) : fresh
274
- workflow.write(path)
214
+ wf = File.exist?(path) ? JiraWorkflow.load(path).refresh(fresh) : fresh
215
+ wf.write(path)
275
216
  action = File.exist?(path) ? 'refreshed' : 'written'
276
217
  PredictabilityEngine.logger.info { "Workflow for profile '#{profile}' #{action}: #{path}" }
277
218
  PredictabilityEngine.logger.info { "Review #{path} and set role: arrival / departure / null per status." }
278
219
  end
279
220
 
280
- desc 'jira_workflow_merge OUTPUT SOURCES...',
221
+ desc 'workflow_merge OUTPUT SOURCES...',
281
222
  'Merge multiple workflow configs into a shared config. Each SOURCE is ' \
282
223
  'either a profile name (resolved to ~/.config/jira/<profile>.workflow.yml) ' \
283
224
  'or an explicit path to a workflow YAML file.'
284
- def jira_workflow_merge(output, *sources)
225
+ def workflow_merge(output, *sources)
285
226
  raise Error, 'Need at least one workflow source to merge' if sources.empty?
286
227
 
287
228
  configs = sources.map do |src|
@@ -291,6 +232,35 @@ module PredictabilityEngine
291
232
  JiraWorkflow.merge(configs).write(output)
292
233
  PredictabilityEngine.logger.info { "Merged workflow from #{sources.join(', ')} written to #{output}" }
293
234
  end
235
+ end
236
+
237
+ class Cli < Thor
238
+ include CliBase
239
+
240
+ package_name "predictability-engine #{VERSION}"
241
+
242
+ desc 'batch SOURCE', 'Run all report formats for the given SOURCE'
243
+ method_option :color, type: :boolean, default: true, desc: 'Enable/disable color output'
244
+ def batch(source)
245
+ Viz.new([], options).all_formats(source)
246
+ end
247
+
248
+ desc 'report SOURCE FORMAT [OUTPUT]', 'Generate a full report in various formats (terminal, html, pdf, md, conf)'
249
+ method_option :color, type: :boolean, default: true, desc: 'Enable/disable color output'
250
+ method_option :clean, type: :boolean, default: true, desc: 'Clean the report directory before generation'
251
+ def report(input_source, format = 'terminal', output = nil)
252
+ if format.to_sym != :terminal && output.nil? && options[:clean]
253
+ ReportGenerator.clean_report_dir(input_source, **options)
254
+ end
255
+ PredictabilityEngine.run_and_print_report(input_source, format, options, output: output)
256
+ end
257
+
258
+ desc 'summary SOURCE', 'Load data from SOURCE and show flow metrics summary'
259
+ method_option :color, type: :boolean, default: true, desc: 'Enable/disable color output'
260
+ def summary(source)
261
+ items = PredictabilityEngine.load_items(source)
262
+ PredictabilityEngine.logger.info { SummaryVisualizer.metrics_terminal(items, color: options[:color]) }
263
+ end
294
264
 
295
265
  desc 'forecast SOURCE BACKLOG_COUNT', 'Run Monte Carlo simulation for BACKLOG_COUNT items'
296
266
  def forecast(source, backlog_count)
@@ -343,18 +313,41 @@ module PredictabilityEngine
343
313
  PredictabilityEngine.logger.info { "Synthetic #{options[:size]} dataset written to #{path}" }
344
314
  end
345
315
 
346
- private
316
+ desc 'viz SUBCOMMAND ...ARGS', 'Visualization commands (individual charts and format-specific reports)'
317
+ subcommand 'viz', Viz
347
318
 
348
- def ask_secret(prompt)
349
- if $stdin.isatty
350
- result = ask(prompt, echo: false)
351
- puts ''
352
- result
319
+ desc 'setup', 'Install/update all dependencies (Ruby gems + Node.js + Playwright + Chromium)'
320
+ def setup
321
+ SetupManager.new.run
322
+ end
323
+
324
+ desc 'ask_ai SOURCE QUESTION', 'Ask the AI assistant about the data in SOURCE'
325
+ def ask_ai(source, question)
326
+ manager = DataManager.new
327
+ manager.load(source)
328
+ assistant = Agents::Assistant.new(manager)
329
+ PredictabilityEngine.logger.info { 'AI Thinking...' }
330
+ response = assistant.ask(question)
331
+ PredictabilityEngine.logger.info { 'AI Response:' }
332
+ PredictabilityEngine.logger.info { '------------' }
333
+ if response.respond_to?(:content)
334
+ PredictabilityEngine.logger.info { response.content }
353
335
  else
354
- ask(prompt)
336
+ PredictabilityEngine.logger.info { response }
355
337
  end
356
338
  end
357
339
 
340
+ desc 'jira SUBCOMMAND ...ARGS', 'Jira connection and workflow configuration'
341
+ subcommand 'jira', Jira
342
+
343
+ desc 'version', 'Print the predictability-engine version'
344
+ def version
345
+ say VERSION
346
+ end
347
+ map '--version' => :version
348
+
349
+ private
350
+
358
351
  def print_calibration_results(result)
359
352
  PredictabilityEngine.logger.info { 'Monte Carlo Hindcast Calibration' }
360
353
  PredictabilityEngine.logger.info { '---------------------------------' }
@@ -386,29 +379,5 @@ module PredictabilityEngine
386
379
  PredictabilityEngine.logger.info { " #{p}% confidence: Done in #{val} days" }
387
380
  end
388
381
  end
389
-
390
- public
391
-
392
- desc 'ask_ai SOURCE QUESTION', 'Ask the AI assistant about the data in SOURCE'
393
- def ask_ai(source, question)
394
- # Assistant needs the manager or at least items.
395
- manager = DataManager.new
396
- manager.load(source)
397
-
398
- assistant = Agents::Assistant.new(manager)
399
- PredictabilityEngine.logger.info { 'AI Thinking...' }
400
- response = assistant.ask(question)
401
-
402
- # response is an array of messages or similar depending on langchain version
403
- # In recent langchainrb versions assistant.run returns the last message
404
- PredictabilityEngine.logger.info { 'AI Response:' }
405
- PredictabilityEngine.logger.info { '------------' }
406
- # Assuming response is a message object with .content
407
- if response.respond_to?(:content)
408
- PredictabilityEngine.logger.info { response.content }
409
- else
410
- PredictabilityEngine.logger.info { response }
411
- end
412
- end
413
382
  end
414
383
  end
@@ -74,6 +74,14 @@ module PredictabilityEngine
74
74
  (@priority_aliases || {})[name.to_s] || name
75
75
  end
76
76
 
77
+ def resolve_path(path)
78
+ return path if Pathname.new(path).absolute? || File.exist?(path)
79
+
80
+ gem_root = File.expand_path('../../..', __dir__)
81
+ gem_relative = File.join(gem_root, path)
82
+ File.exist?(gem_relative) ? gem_relative : path
83
+ end
84
+
77
85
  def mock_data(env_key)
78
86
  json = ENV.fetch(env_key, '[]')
79
87
  JSON.parse(json, symbolize_names: true)
@@ -12,11 +12,12 @@ module PredictabilityEngine
12
12
  }.freeze
13
13
 
14
14
  def perform_load(path)
15
- config = load_csv_config(path)
15
+ resolved = resolve_path(path)
16
+ config = load_csv_config(resolved)
16
17
  @url_prefix ||= config['url_prefix']
17
18
  @done_statuses = load_done_statuses(config)
18
- @source_url = "file://#{File.expand_path(path)}"
19
- CSV.open(path, headers: true, header_converters: :symbol, encoding: 'bom|UTF-8', row_sep: :auto)
19
+ @source_url = "file://#{File.expand_path(resolved)}"
20
+ CSV.open(resolved, headers: true, header_converters: :symbol, encoding: 'bom|UTF-8', row_sep: :auto)
20
21
  .then { |csv| load_data(csv.map { |row| apply_jira_header_map(row.to_h) }) }
21
22
  end
22
23
 
@@ -8,7 +8,7 @@ module PredictabilityEngine
8
8
  def perform_load(path)
9
9
  return build_work_items(mock_data('MOCK_EXCEL_DATA')) if ENV['MOCK_EXCEL_DATA']
10
10
 
11
- xlsx = Roo::Spreadsheet.open(path)
11
+ xlsx = Roo::Spreadsheet.open(resolve_path(path))
12
12
  iterator = xlsx.sheet(0).each(id: 'id', start_date: 'start_date', end_date: 'end_date')
13
13
  .reject { |row| row[:id] == 'id' }
14
14
  load_data(iterator)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PredictabilityEngine
4
- # Mixin providing interactive credential prompts for `jira_config --auth-mode`.
5
- # Relies on `ask` / `ask_secret` being defined by the including class (Thor).
4
+ # Mixin providing interactive credential prompts for `jira config --auth-mode`.
5
+ # Relies on `ask` being available from the including Thor class.
6
6
  module JiraConfigPrompter
7
7
  def build_profile_data(site, context_path, mode)
8
8
  data = { 'site' => site }
@@ -29,6 +29,16 @@ module PredictabilityEngine
29
29
 
30
30
  private
31
31
 
32
+ def ask_secret(prompt)
33
+ if $stdin.isatty
34
+ result = ask(prompt, echo: false)
35
+ puts ''
36
+ result
37
+ else
38
+ ask(prompt)
39
+ end
40
+ end
41
+
32
42
  def prompt_mfa_api_fields
33
43
  field = ask('Token field in login response (default: access_token):').strip
34
44
  { 'email' => ask('Jira email:'),
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PredictabilityEngine
4
- VERSION = '0.8.6'
4
+ VERSION = '0.11.5'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: predictability-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.11.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - cbp-org
@@ -349,16 +349,16 @@ dependencies:
349
349
  name: rake-gem-maintenance
350
350
  requirement: !ruby/object:Gem::Requirement
351
351
  requirements:
352
- - - ">="
352
+ - - "~>"
353
353
  - !ruby/object:Gem::Version
354
- version: '0'
354
+ version: '0.3'
355
355
  type: :development
356
356
  prerelease: false
357
357
  version_requirements: !ruby/object:Gem::Requirement
358
358
  requirements:
359
- - - ">="
359
+ - - "~>"
360
360
  - !ruby/object:Gem::Version
361
- version: '0'
361
+ version: '0.3'
362
362
  - !ruby/object:Gem::Dependency
363
363
  name: redcarpet
364
364
  requirement: !ruby/object:Gem::Requirement
@@ -465,6 +465,8 @@ files:
465
465
  - bin/predictability-engine
466
466
  - bin/predictability-engine.bat
467
467
  - bin/setup
468
+ - bin/setup.bat
469
+ - bin/setup.ps1
468
470
  - data/samples/sample_data.csv
469
471
  - data/samples/sample_data_large.csv
470
472
  - data/samples/wip_data.csv