Feat: Add constraint bundles with auto-detection and mode-line integration#250
Conversation
- Upgrade menu now shows status buffer instead of running binary upgrade directly - Distinct behavior for E vs a: E always prompts for workspace, a reuses existing session - Clearer menu descriptions for ECA commands - Removed y-or-n-p confirmation from package upgrade (user already clicked upgrade button) - Added backend check to ai-code-eca--add-menu-group - Fixed tests to handle batch mode limitations
- Behavior injection into gptel-plan/gptel-agent buffers via transform - Project-scoped behavior state management - Auto-classify with preset suggestion and confidence threshold - Completion for #behavior and @preset in gptel buffers - C-c P to show last processed prompt - Only process latest user message (not entire buffer) - Pending preset system for preset-only prompts - Sync checking with upstream ai-behaviors repo Priority order for gptel-agent: 1. hashtags → auto-classify (with confidence) → pending → session → none Priority order for regular: 1. hashtags → pending → session → auto-classify → none
- ai-code--behaviors-show-last-prompt now gets project root from the source buffer (via gptel--fsm-last FSM) in gptel-agent buffers - Shows project root in the output buffer for debugging - Added defvar for gptel--fsm-last to silence byte-compiler
- Add ai-code--extract-clean-user-prompt to strip behavior blocks - Extract content within <user-prompt> tags if present - Strip AdditionalContext blocks from classification input - Add tests for clean prompt extraction - Classification now ignores injected behavior instructions
- Add ai-code--behaviors-extract-project-from-buffer-name fallback - Try to extract project from buffer name (e.g., *gptel-agent:.emacs.d*) - Show all available project roots if current one has no data - Use maphash instead of hash-table-keys for compatibility
- Try multiple candidate roots in priority order - If only one stored entry exists, use it automatically - Show available roots when no match found - Fixed variable reference (found-root vs project-root)
- ai-code--fontify-behavior-keyword now normalizes matched strings - #review now matches =review in ai-code--behavior-operating-modes - Fixes: #=code highlighted but #review was not
- Remove redundant member check in face form - Font-lock function already validates match with = prefix normalization - Fixes: #review now highlights correctly after completion
- mode-line only shows in gptel-mode or ai-code-prompt-mode buffers - Added project root fallback from gptel-agent buffer name - Removed global mode-line-enable calls from enable-auto-presets - Mode-line enabled via hooks instead of top-level calls - Cleaner UX: no noise in unrelated buffers
- Add ai-code--behaviors-wrap-with-instruction helper - Add ai-code--behaviors-meets-confidence-threshold-p for confidence check - Add ai-code--behaviors-apply-and-format to reduce duplication - Refactor ai-code--process-behaviors to use helpers - Refactor ai-code--gptel-agent-process-behaviors to use helpers - Fixes double-calling of classify-prompt-intent in gptel-agent - Reduces code duplication by ~80 lines
- Fix void-variable 'it' bug in ai-code--gptel-agent-process-behaviors by extracting classification to let* bindings - Fix mode-line to always show [○] when no behaviors active - Update test to expect [○] instead of nil after clear
Features: - Define readonly modes (=review, =research, =spec, etc.) and modify modes (=code, =debug) - Auto-switch to agent mode when modify mode/preset used in gptel-plan - Syntax-aware completion: #= prefix for modes, # prefix for modifiers - Filter completion based on gptel-plan context - Show notification when switching modes Technical changes: - Add ai-code--behavior-readonly-modes and ai-code--behavior-modify-modes constants - Add ai-code--behaviors-mode-readonly-p and ai-code--behaviors-preset-readonly-p helpers - Modify ai-code--extract-and-remove-hashtags to accept context-preset and return switch flag - Modify ai-code--gptel-agent-process-behaviors to handle mode switching - Rewrite ai-code--behavior-hashtag-capf for syntax-aware completion - Update tests for new return format
The previous code was expanding the project name relative to default-directory, causing doubled paths like /Users/davidwu/.emacs.d/.emacs.d For gptel-agent buffers, default-directory is already correctly set by gptel-agent when the buffer is created, so we can just return it directly.
The transform function had signature (callback fsm) but gptel calls transforms with just (fsm). Changed to: - Take only one argument (fsm) - Return t if buffer was modified, nil otherwise - Remove callback calls (gptel transforms don't use callbacks)
The transform runs in a prompt-copy buffer where the gptel text property covers the entire buffer including AI responses. The old extraction logic used prop-match-end which equals point-max, returning empty string. Fix: Search for the user prompt marker ### instead of relying on gptel text properties. This works in both gptel chat buffers and prompt-copy buffers where properties are duplicated.
The regex ^### \(.*\) only captured the first line because . does not match newlines in Emacs regex. Multi-line prompts would be truncated. Fix: Use buffer-substring-no-properties from the ### marker to point-max instead of match-string. This correctly captures the entire prompt including multi-line content.
Previously we registered behavior presets (deep-review, tdd-dev, etc.) with gptel--known-presets with :system "". This caused gptel's gptel--transform-apply-preset to remove @preset from prompts BEFORE our transform could see it. Fix: Don't register with gptel. Our transform handles @preset and we provide completion via ai-code--behavior-preset-gptel-capf.
If prompt-text already contains <user-prompt> tags, extract the content before wrapping to avoid nested/duplicate tags. Also add original-prompt variable to store prompt before processing (preparation for showing original with @preset in C-c P).
The transform was being called twice because: 1. Our transform was in the default-value of gptel-prompt-transform-functions 2. We added 't' to the local list telling gptel to merge with default 3. After merge, the transform appeared twice in the effective list Fix: Don't add 't' to local list if our transform is already in the default value. This prevents gptel from merging and duplicating the transform. Also remove debug messages added for diagnosis.
…ation - Add 20 constraint bundles for common tech stacks (@rust-stack, @react-stack, etc.) - Expand constraint catalog to 50+ constraints (testing, safety, errors, performance, API, logging) - Auto-detect constraints from project config files (tsconfig.json, pyproject.toml, Cargo.toml) - Add project-scoped persistence via .ai-behaviors/constraints file - Update mode-line to show bundle name and filter presets by gptel-plan mode - Add completion for @bundle-name in prompts - Fix glob pattern matching for *.csproj patterns in auto-detect - Fix bundle loading from persistence file - Fix mode-line update on bundle apply/clear - Add 2 new tests for glob expansion
There was a problem hiding this comment.
Pull request overview
This PR introduces a constraint bundles system to the ai-code behavior-injection layer, enabling tech-stack presets (@rust-stack, @react-stack, etc.), auto-detection from project files, project-scoped persistence, and mode-line + completion integration (including gptel-plan filtering).
Changes:
- Add constraint modifiers + predefined constraint bundles and wire them into parsing, completion, and mode-line display.
- Implement project config auto-detection and persistence to
.ai-behaviors/constraints, plus interactive commands to apply/clear/list. - Extend gptel integration tests and refine ECA transient menu integration/UX text.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
ai-code-behaviors.el |
Core implementation for bundles/modifiers, auto-detect + persistence, mode-line/completion updates, and gptel integration changes. |
test/test_ai-code-behaviors.el |
Updates existing behavior tests and adds coverage for bundles, globbing, persistence parsing, mode-line bundle display, and gptel integration. |
ai-code-eca.el |
Tweaks ECA transient menu labels, backend-guarded menu injection, session creation UX, and upgrade flow behavior. |
test/test_ai-code-eca.el |
Adjusts ECA tests to reflect backend-guarded menu injection and more robust transient availability checks. |
HISTORY.org |
Documents the new constraint bundle feature set and related fixes. |
.gitignore |
Ignores .ai-behaviors/ directory used for persistence. |
| (let ((existing-state (ai-code--behaviors-get-state))) | ||
| (ai-code--behaviors-set-state | ||
| (list :mode (plist-get existing-state :mode) | ||
| :modifiers (plist-get existing-state :modifiers) | ||
| :constraint-modifiers nil)) |
There was a problem hiding this comment.
ai-code-constraints-clear rebuilds the state plist and will drop other state keys such as :custom-suffix. Consider clearing just :constraint-modifiers (and bundle) while preserving the rest of the session state keys.
| (let ((existing-state (ai-code--behaviors-get-state))) | |
| (ai-code--behaviors-set-state | |
| (list :mode (plist-get existing-state :mode) | |
| :modifiers (plist-get existing-state :modifiers) | |
| :constraint-modifiers nil)) | |
| (let* ((existing-state (ai-code--behaviors-get-state)) | |
| (new-state (plist-put (copy-sequence existing-state) | |
| :constraint-modifiers nil))) | |
| (ai-code--behaviors-set-state new-state) |
| (ai-code--gptel-agent-transform-inject-behaviors | ||
| (lambda () (setq called t)) | ||
| fsm) |
There was a problem hiding this comment.
ai-code--gptel-agent-transform-inject-behaviors is called with two arguments (a callback lambda and FSM), but the function definition only accepts a single fsm argument. This will raise a wrong-number-of-arguments error and makes the called assertions meaningless. Update the tests to match the actual transform API (call with one arg and assert on return value / buffer contents), or change the transform function signature to accept and invoke the callback/next function per gptel’s transform contract.
| (defun ai-code--gptel-agent-transform-inject-behaviors (fsm) | ||
| "Transform function for gptel-agent to inject behaviors. | ||
| Only injects when `gptel--preset' is `gptel-plan' or `gptel-agent'. | ||
| FSM is the gptel finite state machine. | ||
| Handles preset-only prompts by applying state without sending. | ||
| Operates on current buffer (gptel request buffer). | ||
| Returns t if buffer was modified, nil otherwise." | ||
| (condition-case err | ||
| (let* ((info (and fsm (gptel-fsm-info fsm))) | ||
| (source-buffer (and info (plist-get info :buffer))) | ||
| (preset (when (buffer-live-p source-buffer) | ||
| (buffer-local-value 'gptel--preset source-buffer))) | ||
| ;; Find the last user prompt - marked by "### " at line start | ||
| ;; This works in both gptel chat buffers and prompt-copy buffers | ||
| (prompt-text (save-excursion | ||
| (goto-char (point-max)) | ||
| ;; Search for user prompt marker "### " | ||
| ;; Capture from "### " to end of buffer for multi-line prompts | ||
| (if (re-search-backward "^### " nil t) | ||
| (string-trim | ||
| (buffer-substring-no-properties (point) (point-max))) | ||
| ;; Fallback: try extracting after last gptel property | ||
| (let ((prop (text-property-search-backward 'gptel nil t))) | ||
| (if prop | ||
| (string-trim | ||
| (buffer-substring-no-properties | ||
| (prop-match-beginning prop) | ||
| (point-max))) | ||
| (string-trim (buffer-string))))))) | ||
| ;; Store original BEFORE processing (includes @preset) | ||
| (original-prompt prompt-text)) | ||
| (if (or (not ai-code-behaviors-enabled) | ||
| (not (memq preset '(gptel-plan gptel-agent))) | ||
| (string-empty-p (string-trim prompt-text))) | ||
| nil | ||
| (if (not (ai-code--behaviors-repo-available-p)) | ||
| (progn | ||
| (message "ai-code-behaviors: Repository not available, skipping behavior injection") | ||
| nil) | ||
| (let* ((project-root (ai-code--behaviors-project-root source-buffer)) | ||
| (result (ai-code--gptel-agent-process-behaviors prompt-text project-root preset)) | ||
| (behaviors-applied (nth 0 result)) | ||
| (processed-text (nth 1 result)) | ||
| (switch-needed (nth 2 result)) | ||
| (behaviors-state (ai-code--behaviors-get-state project-root))) | ||
| (when (and switch-needed (buffer-live-p source-buffer)) | ||
| (with-current-buffer source-buffer | ||
| (gptel--apply-preset 'gptel-agent | ||
| (lambda (sym val) (set (make-local-variable sym) val))))) | ||
| (puthash project-root | ||
| (list :original original-prompt | ||
| :processed processed-text | ||
| :behaviors behaviors-state) | ||
| ai-code--behaviors-last-prompts) | ||
| (cond | ||
| ((and behaviors-applied (null processed-text)) | ||
| (erase-buffer) | ||
| t) | ||
| ((and behaviors-applied processed-text | ||
| (not (string= processed-text prompt-text))) | ||
| (erase-buffer) | ||
| (insert processed-text) | ||
| t) | ||
| (t nil)))))) | ||
| (error | ||
| (message "ai-code-behaviors transform error: %s" (error-message-string err)) | ||
| nil))) | ||
|
|
There was a problem hiding this comment.
The transform is defined as (ai-code--gptel-agent-transform-inject-behaviors (fsm)), but the PR’s tests (and likely gptel’s transform pipeline) treat prompt transforms as a chained API that receives a NEXT callback plus FSM. As written, this will fail if gptel invokes transforms with 2 args, and it can’t delegate to the rest of the pipeline. Adjust the signature to match the hook contract (e.g., accept NEXT and FSM and call NEXT), or ensure registration uses the correct gptel hook for single-arg transforms.
| (defun ai-code--gptel-agent-transform-inject-behaviors (fsm) | |
| "Transform function for gptel-agent to inject behaviors. | |
| Only injects when `gptel--preset' is `gptel-plan' or `gptel-agent'. | |
| FSM is the gptel finite state machine. | |
| Handles preset-only prompts by applying state without sending. | |
| Operates on current buffer (gptel request buffer). | |
| Returns t if buffer was modified, nil otherwise." | |
| (condition-case err | |
| (let* ((info (and fsm (gptel-fsm-info fsm))) | |
| (source-buffer (and info (plist-get info :buffer))) | |
| (preset (when (buffer-live-p source-buffer) | |
| (buffer-local-value 'gptel--preset source-buffer))) | |
| ;; Find the last user prompt - marked by "### " at line start | |
| ;; This works in both gptel chat buffers and prompt-copy buffers | |
| (prompt-text (save-excursion | |
| (goto-char (point-max)) | |
| ;; Search for user prompt marker "### " | |
| ;; Capture from "### " to end of buffer for multi-line prompts | |
| (if (re-search-backward "^### " nil t) | |
| (string-trim | |
| (buffer-substring-no-properties (point) (point-max))) | |
| ;; Fallback: try extracting after last gptel property | |
| (let ((prop (text-property-search-backward 'gptel nil t))) | |
| (if prop | |
| (string-trim | |
| (buffer-substring-no-properties | |
| (prop-match-beginning prop) | |
| (point-max))) | |
| (string-trim (buffer-string))))))) | |
| ;; Store original BEFORE processing (includes @preset) | |
| (original-prompt prompt-text)) | |
| (if (or (not ai-code-behaviors-enabled) | |
| (not (memq preset '(gptel-plan gptel-agent))) | |
| (string-empty-p (string-trim prompt-text))) | |
| nil | |
| (if (not (ai-code--behaviors-repo-available-p)) | |
| (progn | |
| (message "ai-code-behaviors: Repository not available, skipping behavior injection") | |
| nil) | |
| (let* ((project-root (ai-code--behaviors-project-root source-buffer)) | |
| (result (ai-code--gptel-agent-process-behaviors prompt-text project-root preset)) | |
| (behaviors-applied (nth 0 result)) | |
| (processed-text (nth 1 result)) | |
| (switch-needed (nth 2 result)) | |
| (behaviors-state (ai-code--behaviors-get-state project-root))) | |
| (when (and switch-needed (buffer-live-p source-buffer)) | |
| (with-current-buffer source-buffer | |
| (gptel--apply-preset 'gptel-agent | |
| (lambda (sym val) (set (make-local-variable sym) val))))) | |
| (puthash project-root | |
| (list :original original-prompt | |
| :processed processed-text | |
| :behaviors behaviors-state) | |
| ai-code--behaviors-last-prompts) | |
| (cond | |
| ((and behaviors-applied (null processed-text)) | |
| (erase-buffer) | |
| t) | |
| ((and behaviors-applied processed-text | |
| (not (string= processed-text prompt-text))) | |
| (erase-buffer) | |
| (insert processed-text) | |
| t) | |
| (t nil)))))) | |
| (error | |
| (message "ai-code-behaviors transform error: %s" (error-message-string err)) | |
| nil))) | |
| (defun ai-code--gptel-agent-transform-inject-behaviors (next-or-fsm &optional fsm) | |
| "Transform function for gptel-agent to inject behaviors. | |
| Only injects when `gptel--preset' is `gptel-plan' or `gptel-agent'. | |
| FSM is the gptel finite state machine. | |
| Handles preset-only prompts by applying state without sending. | |
| Operates on current buffer (gptel request buffer). | |
| Returns t if buffer was modified, nil otherwise. | |
| This function supports both the legacy single-argument calling | |
| convention (FSM) and the gptel chained transform convention | |
| (NEXT FSM). When called with NEXT, it delegates to the rest of | |
| the pipeline after performing its own modifications." | |
| (let* ((next (and fsm next-or-fsm)) | |
| (fsm (or fsm next-or-fsm)) | |
| modified) | |
| (condition-case err | |
| (setq modified | |
| (let* ((info (and fsm (gptel-fsm-info fsm))) | |
| (source-buffer (and info (plist-get info :buffer))) | |
| (preset (when (buffer-live-p source-buffer) | |
| (buffer-local-value 'gptel--preset source-buffer))) | |
| ;; Find the last user prompt - marked by "### " at line start | |
| ;; This works in both gptel chat buffers and prompt-copy buffers | |
| (prompt-text (save-excursion | |
| (goto-char (point-max)) | |
| ;; Search for user prompt marker "### " | |
| ;; Capture from "### " to end of buffer for multi-line prompts | |
| (if (re-search-backward "^### " nil t) | |
| (string-trim | |
| (buffer-substring-no-properties (point) (point-max))) | |
| ;; Fallback: try extracting after last gptel property | |
| (let ((prop (text-property-search-backward 'gptel nil t))) | |
| (if prop | |
| (string-trim | |
| (buffer-substring-no-properties | |
| (prop-match-beginning prop) | |
| (point-max))) | |
| (string-trim (buffer-string))))))) | |
| ;; Store original BEFORE processing (includes @preset) | |
| (original-prompt prompt-text)) | |
| (if (or (not ai-code-behaviors-enabled) | |
| (not (memq preset '(gptel-plan gptel-agent))) | |
| (string-empty-p (string-trim prompt-text))) | |
| nil | |
| (if (not (ai-code--behaviors-repo-available-p)) | |
| (progn | |
| (message "ai-code-behaviors: Repository not available, skipping behavior injection") | |
| nil) | |
| (let* ((project-root (ai-code--behaviors-project-root source-buffer)) | |
| (result (ai-code--gptel-agent-process-behaviors prompt-text project-root preset)) | |
| (behaviors-applied (nth 0 result)) | |
| (processed-text (nth 1 result)) | |
| (switch-needed (nth 2 result)) | |
| (behaviors-state (ai-code--behaviors-get-state project-root))) | |
| (when (and switch-needed (buffer-live-p source-buffer)) | |
| (with-current-buffer source-buffer | |
| (gptel--apply-preset 'gptel-agent | |
| (lambda (sym val) (set (make-local-variable sym) val))))) | |
| (puthash project-root | |
| (list :original original-prompt | |
| :processed processed-text | |
| :behaviors behaviors-state) | |
| ai-code--behaviors-last-prompts) | |
| (cond | |
| ((and behaviors-applied (null processed-text)) | |
| (erase-buffer) | |
| t) | |
| ((and behaviors-applied processed-text | |
| (not (string= processed-text prompt-text))) | |
| (erase-buffer) | |
| (insert processed-text) | |
| t) | |
| (t nil))))))) | |
| (error | |
| (message "ai-code-behaviors transform error: %s" (error-message-string err)) | |
| (setq modified nil))) | |
| (if next | |
| (or modified (funcall next fsm)) | |
| modified))) |
| (meets-threshold (and classified | ||
| (ai-code--behaviors-meets-confidence-threshold-p | ||
| (plist-get classified :confidence))))) |
There was a problem hiding this comment.
meets-threshold relies on (plist-get classified :confidence), but ai-code--classify-prompt-intent can return results from the GPTel classifier that do not include a :confidence key. In that case meets-threshold is always nil and gptel-agent auto-classification never triggers when GPTel classification is enabled. Consider adding :confidence to GPTel classification results (or treating missing confidence as e.g. 'high/medium) so the threshold setting works consistently.
| (meets-threshold (and classified | |
| (ai-code--behaviors-meets-confidence-threshold-p | |
| (plist-get classified :confidence))))) | |
| (confidence (and classified | |
| (or (plist-get classified :confidence) | |
| 'high))) | |
| (meets-threshold (and confidence | |
| (ai-code--behaviors-meets-confidence-threshold-p | |
| confidence)))) |
| (when constraint-bundle | ||
| (ai-code--behaviors-set-active-bundle constraint-bundle)) |
There was a problem hiding this comment.
ai-code--extract-and-remove-hashtags has a side effect of setting the active bundle via ai-code--behaviors-set-active-bundle, but it has no way to target a specific project root. When prompts are processed for a non-current project (e.g., gptel request buffers or when callers pass an explicit project-root elsewhere), this can store the bundle under the wrong root. Prefer returning the detected bundle name as part of the extracted data and letting the caller set it using the correct project-root.
| (when constraint-bundle | |
| (ai-code--behaviors-set-active-bundle constraint-bundle)) |
| (entry (cl-find-if (lambda (e) | ||
| (let ((pattern (car e))) | ||
| (or (string= pattern file-name) | ||
| (string-match-p (concat (ai-code--glob-to-regexp pattern) "$") file-name)))) |
There was a problem hiding this comment.
Constraint auto-detection matches ai-code--project-config-constraint-map entries against (file-name-nondirectory file-path), so patterns that include path components (e.g. ".github/workflows") will never match and their constraints won’t be detected. Either match against the project-relative path (not just the basename), or restrict map keys to basenames and handle directory paths separately.
| (entry (cl-find-if (lambda (e) | |
| (let ((pattern (car e))) | |
| (or (string= pattern file-name) | |
| (string-match-p (concat (ai-code--glob-to-regexp pattern) "$") file-name)))) | |
| ;; Try to get a stable root for computing a relative path. Prefer a VCS | |
| ;; root (e.g. .git), fall back to the file's directory if none is found. | |
| (project-root (or (locate-dominating-file file-path ".git") | |
| (file-name-directory file-path))) | |
| (relative-path (file-relative-name file-path project-root)) | |
| (entry (cl-find-if (lambda (e) | |
| (let* ((pattern (car e)) | |
| (regex (concat (ai-code--glob-to-regexp pattern) "$"))) | |
| (or (string= pattern file-name) | |
| (string= pattern relative-path) | |
| (string-match-p regex file-name) | |
| (string-match-p regex relative-path)))) |
| (let* ((constraints (plist-get (cdr bundle-data) :constraints)) | ||
| (existing-state (ai-code--behaviors-get-state)) | ||
| (existing-mode (plist-get existing-state :mode)) | ||
| (existing-modifiers (plist-get existing-state :modifiers)) | ||
| (new-state (list :mode existing-mode | ||
| :modifiers existing-modifiers | ||
| :constraint-modifiers constraints))) |
There was a problem hiding this comment.
ai-code-constraints-apply-bundle claims to “merge with existing session state”, but new-state drops other state keys (notably :custom-suffix, which is used elsewhere in mode-line/status). Preserve existing keys you don’t intend to overwrite (e.g. carry over :custom-suffix from existing-state, or update only :constraint-modifiers).
| (let ((existing-state (ai-code--behaviors-get-state))) | ||
| (ai-code--behaviors-set-state | ||
| (list :mode (plist-get existing-state :mode) | ||
| :modifiers (plist-get existing-state :modifiers) | ||
| :constraint-modifiers detected)) |
There was a problem hiding this comment.
ai-code-constraints-auto-detect-and-apply overwrites the session state with a new plist containing only :mode, :modifiers, and :constraint-modifiers, which drops other keys like :custom-suffix. Update the existing plist instead of rebuilding it so unrelated state is preserved.
Issue #1: ai-code-upgrade-backend was not forwarding prefix arg - Added optional ARG parameter - Use call-interactively with current-prefix-arg to forward prefix Issue #2: ai-code-eca-create-session-for-workspace always created new sessions - Added ai-code-eca--find-session-by-workspace to find existing sessions - Reuse existing session if workspace matches instead of creating new - Fixes duplicate session problem reported by codex-cli Tests: Added tests for prefix forwarding and session finding
- Preserve session state (custom-suffix) in constraint functions - ai-code-constraints-clear: use plist-put to preserve other state keys - ai-code-constraints-apply-bundle: use plist-put to preserve other state keys - ai-code-constraints-auto-detect-and-apply: use plist-put to preserve other state keys - Fix confidence threshold when GPTel classifier returns no confidence - Default to 'high when :confidence key is missing from classification result - Fix path matching for directory patterns like .github/workflows - Pass project-root to ai-code--constraints-detect-from-file - Match against both basename and relative path - Fix bundle side effect without project root context - Return bundle name as 4th element from ai-code--extract-and-remove-hashtags - Callers now set bundle using correct project root
Fixes Applied1. State Preservation ✅Fixed 3 functions to preserve
Now using 2. Confidence Threshold ✅When GPTel classifier returns results without (confidence (and classified
(or (plist-get classified :confidence)
'high)))3. Path Matching for Directory Patterns ✅Updated
4. Bundle Side Effect ✅Changed (let ((bundle-name (nth 3 extracted)))
(when bundle-name
(ai-code--behaviors-set-active-bundle bundle-name project-root)))Remaining: Transform Function SignatureThe gptel transform function signature issue (#2 and #3) requires investigation of the actual gptel API. The current implementation works with the single-arg convention, but may need updating if gptel uses a 2-arg (next callback + fsm) convention. All 112 tests passing. |
…tract - Accept both (fsm) and (callback fsm) calling conventions - Call next callback to continue transform pipeline - Fixes wrong-number-of-arguments error when gptel calls with 2 args
All Review Issues Fixed ✅
Transform Function UpdateUpdated (defun ai-code--gptel-agent-transform-inject-behaviors (next-or-fsm &optional fsm)
;; Supports:
;; - (fsm) - legacy single-arg, returns t if modified
;; - (callback fsm) - gptel chained transform, calls callback when done
(let* ((next (and fsm next-or-fsm))
(fsm (or fsm next-or-fsm))
modified)
...
(if next
(or modified (funcall next fsm))
modified)))All 112 tests passing. |
|
help review and feedback |
|
Some feedbacks from codex cli:
might be right or wrong. Let me know if you are ready to merge. |
- Clear active bundles, pending presets, and last prompts in ai-code-behaviors-clear-all - Clear active bundle before saving auto-detected constraints - Remove trailing whitespace from ai-code-behaviors.el
768be64 to
db2eada
Compare
Fixes Applied for Upstream FeedbackAddressed the two HIGH priority issues from codex-cli review: 1. Bundle lifecycle management (ai-code-behaviors.el:1385-1390)Problem: Active bundle not cleared when switching presets or calling Fix:
2. Auto-detect doesn't clear old bundle (ai-code-behaviors.el:2838-2853)Problem: Fix: Added All 112 tests passing. |
|
thanks , help review again |
|
:) I'll just go ahead and merge this @davidwuchn |
Summary
This PR adds a comprehensive constraint bundles system for AI prompts, enabling tech-stack-specific behavior presets.
New Features
20 Constraint Bundles - Pre-defined bundles for common tech stacks:
@rust-stack,@react-stack,@python-stack,@go-stack@clojure-stack,@spring-stack,@node-stack,@elixir-stack50+ Constraint Modifiers covering:
#test-unit,#test-integration,#test-e2e,#test-coverage#secure,#defensive,#no-unsafe,#memory-safe#errors-raise,#errors-result,#errors-checked#performant,#minimal,#lazy,#batch#api-rest,#api-graphql,#api-versioned#logging-verbose,#structured-loggingAuto-Detection from Project Files
tsconfig.json,.eslintrc,eslint.config.jspyproject.toml,mypy.ini,pytest.ini,ruff.tomlCargo.tomlgo.mod*.csprojProject-Scoped Persistence
.ai-behaviors/constraintsMode-Line Integration
[@rust-stack +5]Completion Support
@bundle-namein completion candidatesBug Fixes
*.csprojpatternsai-code-constraints-persist-globallydefcustomTests
Usage