fewald-worklog 0.3.19 → 0.3.20

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: 33d69c646f75c4ff235bf6f6d314ae91de000c2043b8369e0427db9d0b80e08f
4
- data.tar.gz: b1d9e80dcc075878a260f6baee8992e8959bcf29786f28575e4e8b918cb704b3
3
+ metadata.gz: 5ca2ba5c3d5bc431914b344733083fd6932fbac57f24a03e13353234cc7695c6
4
+ data.tar.gz: 57046a8bc5b57f68102dec97320536617cf5c0fbdc5042480ca1cdb0b3c2614b
5
5
  SHA512:
6
- metadata.gz: 514b631b513aff4bff09165cf247711bc9739fc210aa0024e4a2e860a2405c0831d9ad650148af0fc5d315339ee87615151411f3b6ca8ad4af80f37b32c2c049
7
- data.tar.gz: 91debf86e2527915247ec3d7e12f2a0335945b2b3ffe7cf7c772bcf5e86b2f9759cf2a93331235652e8af1ea067af2f1d30b85765a01fa62606ce5c958573305
6
+ metadata.gz: 755810f1c1a1fbdae57f3ed6c0b9b16cd1af7c51a7be61a80280087583788aa222e92d27691366f635bd5eea7b0d1ac3583696929a6721486808995b9ef53488
7
+ data.tar.gz: d303ea6a3ab17ffb09317de1b6a15ca378b7dd07722e96ed4fcdc26486e608e499db0e0203845ddc757e04b4a28068bfda4e0e03998d67c208e83affafb38248
data/.version CHANGED
@@ -1 +1 @@
1
- 0.3.19
1
+ 0.3.20
data/lib/cli.rb CHANGED
@@ -134,6 +134,25 @@ class WorklogCLI < Thor
134
134
  end
135
135
  end
136
136
 
137
+ desc 'standup', 'Show the standup report for the current day'
138
+ option :date, type: :string, default: Date.today.to_s,
139
+ desc: <<~DESC
140
+ Show the standup report for a specific date. If this option is provided, --from and --to and --days should not be used.
141
+ DESC
142
+ option :from, type: :string, desc: <<~EOF
143
+ Inclusive start date of the range. Takes precedence over --date, if defined.
144
+ EOF
145
+ option :to, type: :string, desc: <<~EOF
146
+ Inclusive end date of the range. Takes precedence over --date, if defined.
147
+ EOF
148
+ option :days, type: :numeric, desc: <<~EOF
149
+ Number of days to show starting from --date. Takes precedence over --from and --to if defined.
150
+ EOF
151
+ def standup
152
+ worklog = Worklog::Worklog.new
153
+ worklog.standup(options)
154
+ end
155
+
137
156
  desc 'tags', 'Show all tags used in the work log'
138
157
  option :date, type: :string, default: Date.today.to_s,
139
158
  desc: <<~DESC
data/lib/log_entry.rb CHANGED
@@ -108,25 +108,5 @@ module Worklog
108
108
  time == other.time && tags == other.tags && ticket == other.ticket && url == other.url &&
109
109
  epic == other.epic && message == other.message
110
110
  end
111
-
112
- private
113
-
114
- # Prefix for epic entries with formatting.
115
- # @return [String]
116
- def epic_prefix
117
- "#{Rainbow('[EPIC]').bg(:white).fg(:black)} "
118
- end
119
-
120
- # Format metadata for display.
121
- # @return [String]
122
- def format_metadata
123
- metadata_parts = []
124
- metadata_parts << Rainbow(@ticket).fg(:blue) if @ticket
125
- metadata_parts << @tags.join(', ') if @tags&.any?
126
- metadata_parts << @url if @url && @url != ''
127
- metadata_parts << @project if @project && @project != ''
128
-
129
- metadata_parts.empty? ? '' : " [#{metadata_parts.join('] [')}]"
130
- end
131
111
  end
132
112
  end
@@ -85,6 +85,26 @@ module Worklog
85
85
  def format(log_entry)
86
86
  replace_people_handles(log_entry, log_entry.message.dup)
87
87
  end
88
+
89
+ protected
90
+
91
+ # Replace people handles in the message with their names.
92
+ # @param known_people Hash[String, Person] A hash of people with their handles as keys.
93
+ # @param msg [String] the message to replace handles in.
94
+ # @return [String] the message with replaced handles.
95
+ def replace_people_handles(log_entry, msg)
96
+ log_entry.people.each do |person|
97
+ next unless @known_people && @known_people[person]
98
+
99
+ msg.gsub!(/[~@]#{person}/) do |match|
100
+ s = String.new
101
+ s << ' ' if match[0] == ' '
102
+ s << @known_people[person].name if @known_people && @known_people[person]
103
+ s
104
+ end
105
+ end
106
+ msg
107
+ end
88
108
  end
89
109
  end
90
110
  end
data/lib/standup.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dotenv-vault/load'
4
+ require 'erb'
5
+ require 'faraday'
6
+ require 'langchain'
7
+ require 'openai'
8
+ require 'worklogger'
9
+ require 'log_entry_formatters'
10
+
11
+ module Worklog
12
+ # Generates standup prompts based on log entries.
13
+ # The Standup class takes log entries and people information to create a standup message that summarizes the work
14
+ # done, work planned, and blockers.
15
+ #
16
+ # @example
17
+ # # Create log entries (or alternatively load from disk)
18
+ # entries = [LogEntry.new(...), LogEntry.new(...)]
19
+ #
20
+ # # Create people information (or alternatively load from disk)
21
+ # people = { 'user1' => Person.new(...), 'user2' => Person.new(...) }
22
+ #
23
+ # # Create a Standup instance and generate the standup message
24
+ # standup = Standup.new(entries, people)
25
+ # standup.generate
26
+ class Standup
27
+ # Initialize the Standup generator with log entries and people information.
28
+ # If no people information is provided, identifiers will not be resolved to names.
29
+ # @param entries [Array<LogEntry>] the log entries to include in the standup.
30
+ # These entries can be either of a single day or span multiple days, depending on the desired output.
31
+ # They are handled by the LLM to generate a standup message that summarizes the work done, work planned,
32
+ # and blockers.
33
+ # @param people [Hash<String, Person>] the people information to include in the standup
34
+ def initialize(entries, people)
35
+ @entries = entries
36
+ @people = people
37
+ end
38
+
39
+ # Generate the standup message and print it to the console.
40
+ # @return [nil] prints the generated standup message to the console.
41
+ def generate
42
+ Langchain.logger.level = Logger::WARN
43
+
44
+ system_prompt, user_prompt = create_prompt
45
+
46
+ llm = Langchain::LLM::OpenAI.new(api_key: ENV.fetch('OPENAI_API_KEY', nil),
47
+ default_options: {
48
+ chat_model: 'gpt-5.2' # or any other supported model
49
+ })
50
+
51
+ # Override the client to increase timeout
52
+ def llm.client
53
+ @client ||= Faraday.new(url: url, headers: auth_headers) do |conn|
54
+ conn.options.timeout = 300
55
+ conn.request :json
56
+ conn.response :json
57
+ conn.response :raise_error
58
+ conn.response :logger, Langchain.logger, { headers: true, bodies: true, errors: true }
59
+ end
60
+ end
61
+
62
+ assistant = Langchain::Assistant.new(
63
+ llm: llm,
64
+ instructions: system_prompt
65
+ )
66
+
67
+ assistant.add_message(role: 'user', content: user_prompt)
68
+
69
+ WorkLogger.debug('Starting standup generation')
70
+ begin
71
+ assistant.run(auto_tool_execution: true)
72
+ rescue Faraday::ForbiddenError => e
73
+ WorkLogger.error("LLM request failed: #{e.response}")
74
+ raise
75
+ end
76
+ WorkLogger.debug('Finished standup generation')
77
+ puts Rainbow('Standup generated successfully!').yellow
78
+ puts assistant.messages.last.content
79
+ end
80
+
81
+ # Create the system and user prompts for standup generation.
82
+ # @return [Array<String>] the system prompt and user prompt.
83
+ def create_prompt
84
+ system_prompt_template = File.read(File.join(__dir__, '..', 'assets', 'prompts', 'standup.system.md.erb'))
85
+ system_prompt = ERB.new(system_prompt_template, trim_mode: '-<>').result(binding)
86
+
87
+ user_prompt_template = File.read(File.join(__dir__, '..', 'assets', 'prompts', 'standup.user.md.erb'))
88
+ user_prompt = ERB.new(user_prompt_template, trim_mode: '-<>').result(binding)
89
+
90
+ [system_prompt, user_prompt]
91
+ end
92
+
93
+ def formatted_entries
94
+ formatter = LogEntryFormatters::SimpleFormatter.new(@people)
95
+ @entries.map do |entry|
96
+ entry.to_hash.slice(:epic, :ticket, :url).merge(message: formatter.format(entry))
97
+ end
98
+ end
99
+ end
100
+ end
data/lib/worklog.rb CHANGED
@@ -16,6 +16,7 @@ require 'string_helper'
16
16
  require 'people_storage'
17
17
  require 'printer'
18
18
  require 'project_storage'
19
+ require 'standup'
19
20
  require 'statistics'
20
21
  require 'storage'
21
22
  require 'summary'
@@ -153,7 +154,6 @@ module Worklog
153
154
 
154
155
  events_by_date.each do |date, events|
155
156
  WorkLogger.info Rainbow("Found #{events.size} events for #{date}").green
156
- puts "Processing #{events.size} events for '#{date}'"
157
157
  @storage.create_file_skeleton(date)
158
158
  daily_log = @storage.load_log!(@storage.filepath(date))
159
159
  dirty = false
@@ -163,7 +163,7 @@ module Worklog
163
163
 
164
164
  # Check if entry already exists
165
165
  if daily_log.key?(log_entry.key)
166
- WorkLogger.debug('Entry already exists, skipping')
166
+ WorkLogger.debug("Entry with key #{log_entry.key} already exists in #{log_entry.time.to_date}, skipping")
167
167
  else
168
168
  daily_log << log_entry
169
169
  # Mark log as dirty to trigger write later
@@ -176,7 +176,7 @@ module Worklog
176
176
  if dirty
177
177
  @storage.write_log(@storage.filepath(date), daily_log)
178
178
  else
179
- WorkLogger.info Rainbow("No new entries to add for #{date}, skipping write.").yellow
179
+ WorkLogger.info "No new entries to add for #{date}, skipping write."
180
180
  end
181
181
  end
182
182
  end
@@ -333,6 +333,12 @@ module Worklog
333
333
  puts 'No projects found.' if projects.empty?
334
334
  end
335
335
 
336
+ def standup(options = {})
337
+ start_date, end_date = start_end_date(options)
338
+ entries = @storage.days_between(start_date, end_date).map(&:entries).flatten
339
+ Standup.new(entries, @people).generate
340
+ end
341
+
336
342
  # Show all tags used in the work log or details for a specific tag
337
343
  #
338
344
  # @param tag [String, nil] the tag to show details for, or nil to show all tags
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fewald-worklog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.19
4
+ version: 0.3.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Friedrich Ewald
@@ -156,6 +156,7 @@ files:
156
156
  - lib/printer.rb
157
157
  - lib/project.rb
158
158
  - lib/project_storage.rb
159
+ - lib/standup.rb
159
160
  - lib/statistics.rb
160
161
  - lib/storage.rb
161
162
  - lib/string_helper.rb