Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
0554fee
feat(agent): add agent guidelines
mfo Sep 13, 2025
1905847
chore(bundle): +langchainrb, openai & anthropic
colinux Feb 3, 2025
3b6b8b6
feat(revision): description for llm
colinux Feb 3, 2025
aa00899
feat(llm): backup alternative version of schema and prompt
colinux Feb 3, 2025
7d370e8
feat: make a llm suggesting a better revision
colinux Feb 3, 2025
7ff585b
feat(llm): improve prompts
colinux Feb 4, 2025
6f137a9
feat: first skeleton for AI simplification
LeSim Feb 3, 2025
bfa846a
feat: use LLM::RevisionImproverService
LeSim Feb 4, 2025
2eb7527
feat(procedure.lint): add linter to gauge llm suggestions
mfo Feb 4, 2025
52dca27
feat(procedure::card): add quality tile
mfo Feb 5, 2025
d7b8411
add magic wand
LeSim Feb 4, 2025
14d77cd
better view
LeSim Feb 4, 2025
dc5bd1c
still better view
LeSim Feb 5, 2025
5887c20
fix(llm): improve a little bit prompts
colinux Feb 4, 2025
891b636
feat(llm): add conditional
colinux Feb 4, 2025
422a658
feat(llm): stream response
colinux Feb 4, 2025
8e8169e
feat(llm): improve prompt again
colinux Feb 5, 2025
af07951
fix apply_changes
LeSim Feb 5, 2025
ee69b1e
feat(llm): split suggestion in 2 parts
colinux Feb 5, 2025
b90836a
feat(llm): add quality score gauge and enhance ProcedureLinter
mfo Feb 5, 2025
981b9d6
feat(TypesDeChampController#suggest): stub/unstub for demo
mfo Feb 6, 2025
8f01968
wip
mfo Feb 13, 2025
4c4ab8f
fix(merge): fail
mfo May 28, 2025
a2e5768
spec(types_de_champ_controller#simplify): add spec for the simplify a…
mfo Sep 4, 2025
48369e1
feat(ProcedureRevision#apply_changes): add spec for apply_changes
mfo Sep 4, 2025
9ee506b
spec(procedure_linter): add spec for the procedure_linter
mfo Sep 4, 2025
01a9b62
refactor(revision_improper_service): rework service that simplifies
mfo Sep 4, 2025
4bd62e2
feat(RevisionImproperService): strip json fence since LLM like to wra…
mfo Sep 4, 2025
df58ba8
feat(views/administrateurs/types_de_champ/simplify): adapt UI to make…
mfo Sep 4, 2025
78b70e8
spec(TypeDeChampController#accept_simplification): spec action
mfo Sep 5, 2025
c5ba5bf
wip: select changes
mfo Sep 5, 2025
59131ac
llm(prompt): rework prompt to align delete/destroy, also ensure summa…
mfo Sep 5, 2025
3655f0d
stub(mfo): using deepseek, it works, will see later for more acceptab…
mfo Sep 5, 2025
155fa73
feat(LLM): rewire LLM call to wrap them in background jobs
mfo Sep 9, 2025
88a04b1
feat(model/llm_rule_suggestion & model/llm_rule_suggestion_item): add…
mfo Sep 9, 2025
0322d9c
feat(llm_enqueue_nightly_improve_procedure_job): make it idempotent
mfo Sep 9, 2025
5909047
feat(generate_improve_label_job): rework signature to take suggestion
mfo Sep 9, 2025
d76db27
feat(LLM::Runner): add an llm runner calling tools to normalize events
mfo Sep 10, 2025
af4830b
feat(LLM::LabelImprover): standardize label improvements via a tool call
mfo Sep 10, 2025
d0a7254
feat(LLM::GenerateImproveLabelJob): call for llm service and populate…
mfo Sep 10, 2025
ae098e9
feat(Administrateurs::TypesDeChampController): switch simplify per ru…
mfo Sep 15, 2025
db4b416
feat(LLM::ImproveLabelComponent): extract ImproveLabel rendering with…
mfo Sep 24, 2025
8e4c4c8
feat(Administrateurs::TypesDeChampController#simplify_index): list ll…
mfo Sep 24, 2025
a5569f1
feat(LLM::GenerateImproveLabelJob): never re-enqueue a failed job, we…
mfo Sep 25, 2025
cdb57d8
refactor(simplification): use new models all across the simplificatio…
mfo Sep 29, 2025
f5ccc5c
refactor(LabelImprover): extract base llm class for toolcalling/norma…
mfo Sep 29, 2025
c998b1c
feat(BaseImprover.generate_for): change method signature to take an L…
mfo Sep 29, 2025
6a04e25
refactor(LLM::GenerateImproveLabelJob): refactor LLM::GenerateImprove…
mfo Sep 29, 2025
829e62b
feat(LLM::StructureImprover): add structure improve basic prompt
mfo Sep 29, 2025
f355852
feat(LLMEnqueueNightlyImproveProcedureJob): wire new ImproveStructure
mfo Sep 30, 2025
b0359fb
feat(ProcedureRevision#apply_changes): handle changes from ImproveStu…
mfo Oct 1, 2025
0dd81bb
feat(Cron::LLMEnqueueNightlyImproveProcedureJob): split in two jobs s…
mfo Oct 1, 2025
690e066
feat(LLMSuggestions): wire on draft revision
mfo Oct 1, 2025
2a81346
chore(clean): former linter
mfo Oct 1, 2025
e1c9bbf
feat(LLMSuggestionRule): allow admin to regenerate the suggestions
mfo Oct 1, 2025
dbdf692
refactor(LLM::Components): extract a form component to dry things up …
mfo Oct 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Repository Guidelines

## Project Structure & Module Organization
- `app/`: Rails MVC, views (HAML), components, and `app/javascript` (Vite/React/TS).
- `spec/`: RSpec tests (models, controllers, system, GraphQL, etc.).
- `lib/`: internal libraries and cops; `config/`: environments, routes, initializers.
- `db/`: migrations and seeds; `public/`: static assets; `bin/`: helper scripts.
- Docs in `doc/` and API docs tooling in `api_doc/`.

## Build, Test, and Development Commands
- Setup: `bin/setup` (deps, DB, assets). Update: `bin/update`.
- Run locally: `bin/dev` (Overmind runs web, jobs, and `bin/vite`). Alt: `overmind start -f Procfile.dev`.
- Ruby tests: `bin/rspec` or `bin/rake spec`. Example: `bin/rspec spec/models/user_spec.rb:12`.
- JS tests: `bun run test` (Vitest). Coverage: `bun run coverage`.
- Lint all: `bin/rake lint`. Specific: `bundle exec rubocop`, `bun run lint:js`, `bun run lint:types`, `bun run lint:css`.

## Coding Style & Naming Conventions
- Ruby: 2-space indent, Rails/RSpec cops via RuboCop (`.rubocop.yml`). Files `snake_case.rb`; classes `CamelCase`.
- Views: HAML checked by `haml-lint`.
- Frontend: TypeScript + React via Vite. Use PascalCase for components, camelCase for variables; keep code in `app/javascript`.
- Formatting: Prettier for styles and TS; ESLint rules in `eslint.config.ts`.

## Testing Guidelines
- Frameworks: RSpec (+ Capybara/Playwright) and Vitest.
- Ruby specs end with `_spec.rb` under `spec/…`. System specs may require Chrome; run visibly: `NO_HEADLESS=1 bin/rspec spec/system`.
- JS unit tests live alongside code as `*.test.ts(x)`.
- Coverage: SimpleCov (Ruby) and Vitest coverage; keep meaningful assertions.

## Commit & Pull Request Guidelines
- Commits: imperative mood, focused scope; reference issues (e.g., `Fix: redeliver webhooks (#123)`).
- PRs: clear description, linked issues, screenshots for UI changes, migration notes, and added/updated tests. Ensure CI is green.

## Security & Configuration Tips
- Do not commit secrets. Use `.env`/`.env.test` (dotenv) locally.
- Prereqs: PostgreSQL ≥ 15, Redis (Sidekiq), ImageMagick with restricted policy. Sidekiq dev: `overmind start -f Procfile.sidekiq.dev`.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ gem 'after_commit_everywhere'
gem 'after_party'
gem 'ancestry'
gem 'anchored'
gem 'anthropic'
gem 'bcrypt'
gem 'bootsnap', '>= 1.4.4', require: false # Reduces boot times through caching; required in config/boot.rb
gem 'browser'
Expand Down Expand Up @@ -61,6 +62,7 @@ gem 'json_schemer'
gem 'jwt'
gem 'kaminari'
gem 'kredis'
gem 'langchainrb'
gem 'listen' # Required by ActiveSupport::EventedFileUpdateChecker
gem 'lograge'
gem 'logstash-event'
Expand Down Expand Up @@ -92,6 +94,7 @@ gem 'redcarpet'
gem 'redis'
gem 'rexml' # add missing gem due to ruby3 (https://github.com/Shopify/bootsnap/issues/325)
gem 'rqrcode'
gem 'ruby-openai'
gem 'saml_idp'
gem 'sassc-rails' # Use SCSS for stylesheets
gem 'sentry-delayed_job'
Expand Down
44 changes: 26 additions & 18 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ GEM
ancestry (4.3.3)
activerecord (>= 5.2.6)
anchored (1.1.0)
anthropic (0.3.2)
event_stream_parser (>= 0.3.0, < 2.0.0)
faraday (>= 1)
faraday-multipart (>= 1)
anyway_config (2.7.2)
ruby-next-core (~> 1.0)
ast (2.4.3)
Expand All @@ -149,6 +153,7 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
baran (0.1.12)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.4.1)
Expand Down Expand Up @@ -267,6 +272,7 @@ GEM
tzinfo
ethon (0.17.0)
ffi (>= 1.15.0)
event_stream_parser (1.0.0)
excon (1.3.0)
logger
factory_bot (6.5.5)
Expand All @@ -280,6 +286,8 @@ GEM
faraday-jwt (0.1.0)
faraday (~> 2.0)
json-jwt (~> 1.16)
faraday-multipart (1.1.0)
multipart-post (~> 2.0)
faraday-net_http (3.4.1)
net-http (>= 0.5.0)
ffi (1.17.2)
Expand All @@ -288,9 +296,6 @@ GEM
ffi (1.17.2-arm-linux-gnu)
ffi (1.17.2-arm-linux-musl)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
fiber-storage (1.0.1)
flipper (1.3.6)
concurrent-ruby (< 2)
Expand Down Expand Up @@ -420,6 +425,8 @@ GEM
bindata
faraday (~> 2.0)
faraday-follow_redirects
json-schema (4.3.1)
addressable (>= 2.8)
json_schemer (2.4.0)
bigdecimal
hana (~> 1.3)
Expand All @@ -446,6 +453,13 @@ GEM
activemodel (>= 6.0.0)
activesupport (>= 6.0.0)
redis (>= 4.2, < 6)
langchainrb (0.19.3)
baran (~> 0.1.9)
csv
json-schema (~> 4)
matrix
pragmatic_segmenter (~> 0.3.0)
zeitwerk (~> 2.5)
language_server-protocol (3.17.0.5)
launchy (3.1.1)
addressable (~> 2.8)
Expand Down Expand Up @@ -504,6 +518,7 @@ GEM
multi_json (1.17.0)
multi_xml (0.7.2)
bigdecimal (~> 3.1)
multipart-post (2.4.1)
mustermann (3.0.4)
ruby2_keywords (~> 0.0.1)
mutex_m (0.3.0)
Expand Down Expand Up @@ -532,12 +547,6 @@ GEM
racc (~> 1.4)
nokogiri (1.18.9-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.9-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-musl)
racc (~> 1.4)
oauth2 (2.0.17)
faraday (>= 0.17.3, < 4.0)
jwt (>= 1.0, < 4.0)
Expand Down Expand Up @@ -578,18 +587,14 @@ GEM
racc
pdf-core (0.9.0)
pg (1.6.2)
pg (1.6.2-aarch64-linux)
pg (1.6.2-aarch64-linux-musl)
pg (1.6.2-arm64-darwin)
pg (1.6.2-x86_64-darwin)
pg (1.6.2-x86_64-linux)
pg (1.6.2-x86_64-linux-musl)
phonelib (0.10.12)
playwright-ruby-client (1.55.0)
concurrent-ruby (>= 1.1.6)
mime-types (>= 3.0)
pp (0.6.2)
prettyprint
pragmatic_segmenter (0.3.24)
prawn (2.4.0)
pdf-core (~> 0.9.0)
ttfunk (~> 1.7)
Expand Down Expand Up @@ -788,6 +793,10 @@ GEM
ruby-graphviz (1.2.5)
rexml
ruby-next-core (1.1.2)
ruby-openai (7.3.1)
event_stream_parser (>= 0.3.0, < 2.0.0)
faraday (>= 1)
faraday-multipart (>= 1)
ruby-pg-extras (5.6.13)
pg
terminal-table
Expand Down Expand Up @@ -1002,10 +1011,6 @@ GEM
zxcvbn (1.0.0)

PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin
ruby
x86_64-darwin
Expand All @@ -1024,6 +1029,7 @@ DEPENDENCIES
after_party
ancestry
anchored
anthropic
axe-core-rspec
bcrypt
benchmark-ips
Expand Down Expand Up @@ -1082,6 +1088,7 @@ DEPENDENCIES
jwt
kaminari
kredis
langchainrb
launchy
letter_opener_web
listen
Expand Down Expand Up @@ -1131,6 +1138,7 @@ DEPENDENCIES
rubocop-performance
rubocop-rails
rubocop-rspec
ruby-openai
saml_idp
sassc-rails
selenium-devtools
Expand Down
15 changes: 15 additions & 0 deletions app/components/llm/improve_label_item_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="fr-checkbox-group fr-my-1v fr-my-2w">
<%= checkbox do %>
<%= original_tdc.libelle %>
<% if libelle_changed? %>
<span class="fr-ml-1w">→ <%= payload['libelle'] %></span>
<% end %>

<% if item.justification.present? %>
<span class="fr-hint-text">
<%= confidence_badge if item.confidence.present? %>
<%= item.justification %>
</span>
<% end %>
<% end %>
</div>
37 changes: 37 additions & 0 deletions app/components/llm/improve_label_item_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

class LLM::ImproveLabelItemComponent < LLM::SuggestionItemComponent
ACCEPTED_VALUE = LLMRuleSuggestionItem.verify_statuses.fetch(:accepted)
SKIPPED_VALUE = LLMRuleSuggestionItem.verify_statuses.fetch(:skipped)

def render?
original_tdc.present?
end

def original_tdc
@original_tdc ||= tdc_for(item.stable_id)
end

def payload
@payload ||= item.payload || {}
end

def libelle_changed?
payload['libelle'].present? && payload['libelle'] != original_tdc.libelle
end

def confidence_badge
return unless item.confidence.present?

content_tag(:span, "confiance: #{item.confidence}", class: 'fr-badge')
end

def checkbox
safe_join([
form_builder.check_box(:verify_status, {}, ACCEPTED_VALUE, SKIPPED_VALUE),
form_builder.label(:verify_status, class: 'fr-label') do
capture { yield if block_given? }
end
])
end
end
44 changes: 44 additions & 0 deletions app/components/llm/improve_structure_item_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="fr-card fr-card--sm fr-my-2w">
<div class="fr-card__body">
<div class="fr-card__title">
<% if op_kind == 'add' %>
<span class="fr-badge fr-badge--info fr-mr-1w">Ajout de section</span>
<%= payload['libelle'] %>
<% elsif op_kind == 'update' && original_tdc %>
<span class="fr-badge fr-badge--warning fr-mr-1w">Réorganisation</span>
<%= original_tdc.libelle %>
<% end %>
</div>

<div class="fr-card__desc">
<ul class="fr-pl-0">
<% if op_kind == 'update' %>
<li><strong>Nouvelle position :</strong> <%= position %></li>
<% if payload.key?('mandatory') %>
<li><strong>Champ obligatoire :</strong> <%= mandatory? ? 'Oui' : 'Non' %></li>
<% end %>
<% elsif op_kind == 'add' %>
<li><strong>Position :</strong> <%= position %></li>
<% if type_champ.present? %>
<li><strong>Type :</strong> <%= type_champ %></li>
<% end %>
<% end %>
</ul>

<% if item.justification.present? %>
<p class="fr-hint-text fr-mt-2w">
<%= confidence_badge %>
<%= item.justification %>
</p>
<% end %>
</div>
</div>

<div class="fr-card__footer">
<div class="fr-checkbox-group">
<%= checkbox do %>
Appliquer cette modification
<% end %>
</div>
</div>
</div>
45 changes: 45 additions & 0 deletions app/components/llm/improve_structure_item_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

class LLM::ImproveStructureItemComponent < LLM::SuggestionItemComponent
ACCEPTED_VALUE = LLMRuleSuggestionItem.verify_statuses.fetch(:accepted)
SKIPPED_VALUE = LLMRuleSuggestionItem.verify_statuses.fetch(:skipped)

def original_tdc
@original_tdc ||= tdc_for(item.stable_id)
end

def payload
@payload ||= item.payload || {}
end

def op_kind
item.op_kind
end

def position
payload['position']
end

def mandatory?
payload['mandatory']
end

def type_champ
payload['type_champ']
end

def confidence_badge
return unless item.confidence.present?

content_tag(:span, "confiance: #{item.confidence}", class: 'fr-badge fr-mr-1w')
end

def checkbox(label_class: 'fr-label')
safe_join([
form_builder.check_box(:verify_status, {}, ACCEPTED_VALUE, SKIPPED_VALUE),
form_builder.label(:verify_status, class: label_class) do
capture { yield if block_given? }
end
])
end
end
22 changes: 22 additions & 0 deletions app/components/llm/suggestion_form_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h2 class="fr-h3 mt-4"><%= title %></h2>
<%= render Dsfr::CalloutComponent.new(title: "À quoi sert cette règle ?", theme: :neutral) do |callout| %>
<% callout.with_body do %>
<p><%= summary %></p>
<% end %>
<% end %>

<%= form_for llm_rule_suggestion,
url: accept_simplification_admin_procedure_types_de_champ_path(procedure, llm_rule_suggestion),
method: :post,
data: form_data do |form| %>
<% llm_rule_suggestion.llm_rule_suggestion_items.each do |llm_rule_suggestion_item| %>
<%= form.fields_for :llm_rule_suggestion_items, llm_rule_suggestion_item do |ff| %>
<%= render item_component.new(form_builder: ff) %>
<% end %>
<% end %>

<ul class="fr-btns-group fr-btns-group--inline-md mt-4">
<li><%= form.submit("Accepter les modifications", class: "fr-btn") %></li>
<li><%= link_to("Revenir aux règles", back_link, class: "fr-btn fr-btn--secondary") %></li>
</ul>
<% end %>
Loading