Repository navigation
feat(core): deterministic model patching, new component detection, and remote model repo support - #15
Merged
Merged
Conversation
The old Stage 4 used an expensive advanced-model AI call to generate raw architecture code, which could invent non-existent components and required --generate-model to be passed explicitly. Replace it with a deterministic patcher that derives relationship changes from the Stage 3 structured analysis output, validates component IDs against the loaded model, deduplicates against existing relationships, and patches model files in-place using a cheap fast-model call. --open-pr now always uses this path. - Add LikeC4 and Structurizr model patchers with full test suites - Add StructuredRelationship schema for typed relationship data - Add PR creation pipeline helper and patch PR body formatter - Remove --generate-model flag from CLI, CI entry, and entrypoints - Remove generateArchitectureCode from AIProvider and BaseProvider - Remove Stage 4 prompt templates, formatters, and prompt variables - Remove generatedFileExtension from adapter metadata - Remove allComponents/modelFormat from DriftAnalysisResult - Update all documentation to reflect the three-stage pipeline
…model targeting Relocate patchers from patching/ into each adapter directory and add DSL validation (LikeC4 via workspace parse, Structurizr via CLI export) so patched output is verified before committing. Platform writers gain closeChangeRequest() to auto-close model PRs when a re-analysis finds no violations. New --model-repo flag lets CI target a different repository for model update PRs. PR body now links back to the source analysis PR for traceability.
Decouple model patching from PR creation by introducing Stage 4 as an independent pipeline step, gated by a new --patch flag. Move all publishing actions (PR creation, commenting, GH Actions outputs) into a dedicated publishResults() function in publish.ts, reducing analyze.ts by ~120 lines and allowing local model patching without --open-pr.
Extend VERBOSE=true stderr logging to all remaining blind spots: Structurizr patcher, base provider, retry utility, quickValidatePatch, and pipeline stages 2-3.
…ding Both patcher implementations shared ~80% identical code. Extract shared logic into BasePatcher abstract class and withTempWorkspaceCopy utility, reducing ~250 lines of duplication. Also fixes a bug in analysisHasFindings where ?? operator ignored remove-only model updates (false is not nullish), extracts parseOwnerRepo and buildDiffFromFiles helpers, and removes a redundant mutation in the analyze pipeline. Adds ~40 new tests covering quickValidatePatch, analysisHasFindings, pipeline orchestration (analyze, publish, pr-creation), and provider patchModel methods.
The openai-api-key GitHub Action input was silently dropped because entrypoint.sh never exported it. Also adds --model-repo to the interactive CLI (previously only in ci-entry), documents Stage 4 and the publish pipeline in web docs, and updates the architecture model with the new publish component and relationships.
Add --model-ref flag and rework --model-repo to accept repository URLs (or owner/repo slugs) pointing to the architecture model source. The analyze pipeline now clones the model repo to a temp directory, runs the full analysis, and cleans up on exit. Also improves generated PR titles with source repo and PR title context, fixes git path resolution in BasePatcher for temp directories, surfaces generatedChangeRequest in AnalyzeResult, and adds a post-action step to the interactive wizard.
Add `open-pr: 'auto'` option that creates model PRs only on demand via `/erode update-model` PR comments, then keeps them updated on subsequent pushes. Branch naming now includes source repo slug for cross-repo support. Entrypoint reads PR URL from issue_comment events. Harden AI-generated content handling: strict Zod schemas for component IDs and DSL text, quote stripping in patchers, git ref validation, path traversal checks, credential stripping from error messages, single-quote token escaping in askpass scripts, and Bitbucket pagination origin validation.
The entrypoint cloned the model repo itself, then passed both the absolute clone path and --model-repo to the core CLI. The core's resolveModelSource() cloned again, joining paths into an invalid nested path like /tmp/erode-model-XXXXX/tmp/model-repo/likec4. Let the core handle cloning via resolveModelSource() and pass --model-ref so the branch reaches it.
Move the outbound-call exception directly into the "Internal API Changes" ignore section so the AI sees it at decision time, not after classifying handlers as internal. Remove the ineffective standalone IMPORTANT CLARIFICATION section. Add gateway proxy example. Add test to guard against accidental removal. Ref: DRI-21 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend the drift analysis pipeline to identify brand-new services and applications introduced by a PR that are not yet in the architecture model. The AI now receives the full component index and relationship list, enabling it to propose newComponents alongside relationships. Key changes: - NewComponentSchema with DSL injection guards (id, kind, name, tags) - Stricter dslIdentifierPattern for relationship/component kind fields - LikeC4 and Structurizr patchers generate component DSL lines - Patchers track and surface validationSkipped when tooling unavailable - PR comment and patch PR body render new components prominently - Stage 4 handles new components alongside relationship patching
… attribution - --patch-local was writing to repo-relative path instead of absolute, causing writes to fail when CWD differs from model root - Component DSL line attribution used string matching, which misattributed lines when one component ID was a substring of another (e.g. "api" vs "api-gateway"); now uses positional indexing - Replace execSync with execFileSync to avoid shell injection - Add verbose debug logging in model-source catch blocks - Document --model-ref flag and improve docs voice
Pass PR file list to the drift analysis prompt so the AI can reference actual changed paths when detecting new components, instead of relying solely on the diff content.
parse
added a commit
that referenced
this pull request
Mar 3, 2026
…d remote model repo support (#15) * feat(core): replace AI model generation with deterministic patching The old Stage 4 used an expensive advanced-model AI call to generate raw architecture code, which could invent non-existent components and required --generate-model to be passed explicitly. Replace it with a deterministic patcher that derives relationship changes from the Stage 3 structured analysis output, validates component IDs against the loaded model, deduplicates against existing relationships, and patches model files in-place using a cheap fast-model call. --open-pr now always uses this path. - Add LikeC4 and Structurizr model patchers with full test suites - Add StructuredRelationship schema for typed relationship data - Add PR creation pipeline helper and patch PR body formatter - Remove --generate-model flag from CLI, CI entry, and entrypoints - Remove generateArchitectureCode from AIProvider and BaseProvider - Remove Stage 4 prompt templates, formatters, and prompt variables - Remove generatedFileExtension from adapter metadata - Remove allComponents/modelFormat from DriftAnalysisResult - Update all documentation to reflect the three-stage pipeline * feat(core): add DSL validation, auto-close stale PRs, and cross-repo model targeting Relocate patchers from patching/ into each adapter directory and add DSL validation (LikeC4 via workspace parse, Structurizr via CLI export) so patched output is verified before committing. Platform writers gain closeChangeRequest() to auto-close model PRs when a re-analysis finds no violations. New --model-repo flag lets CI target a different repository for model update PRs. PR body now links back to the source analysis PR for traceability. * refactor(core): extract Stage 4 and publish pipeline from analyze Decouple model patching from PR creation by introducing Stage 4 as an independent pipeline step, gated by a new --patch flag. Move all publishing actions (PR creation, commenting, GH Actions outputs) into a dedicated publishResults() function in publish.ts, reducing analyze.ts by ~120 lines and allowing local model patching without --open-pr. * feat(core): add verbose debug logging across the pipeline Extend VERBOSE=true stderr logging to all remaining blind spots: Structurizr patcher, base provider, retry utility, quickValidatePatch, and pipeline stages 2-3. * refactor(core): extract BasePatcher and shared DSL validation scaffolding Both patcher implementations shared ~80% identical code. Extract shared logic into BasePatcher abstract class and withTempWorkspaceCopy utility, reducing ~250 lines of duplication. Also fixes a bug in analysisHasFindings where ?? operator ignored remove-only model updates (false is not nullish), extracts parseOwnerRepo and buildDiffFromFiles helpers, and removes a redundant mutation in the analyze pipeline. Adds ~40 new tests covering quickValidatePatch, analysisHasFindings, pipeline orchestration (analyze, publish, pr-creation), and provider patchModel methods. * fix: export OPENAI_API_KEY in entrypoint, document publish pipeline The openai-api-key GitHub Action input was silently dropped because entrypoint.sh never exported it. Also adds --model-repo to the interactive CLI (previously only in ci-entry), documents Stage 4 and the publish pipeline in web docs, and updates the architecture model with the new publish component and relationships. * feat: support cloning remote model repositories for analysis Add --model-ref flag and rework --model-repo to accept repository URLs (or owner/repo slugs) pointing to the architecture model source. The analyze pipeline now clones the model repo to a temp directory, runs the full analysis, and cleans up on exit. Also improves generated PR titles with source repo and PR title context, fixes git path resolution in BasePatcher for temp directories, surfaces generatedChangeRequest in AnalyzeResult, and adds a post-action step to the interactive wizard. * feat: add open-pr auto mode and harden input validation Add `open-pr: 'auto'` option that creates model PRs only on demand via `/erode update-model` PR comments, then keeps them updated on subsequent pushes. Branch naming now includes source repo slug for cross-repo support. Entrypoint reads PR URL from issue_comment events. Harden AI-generated content handling: strict Zod schemas for component IDs and DSL text, quote stripping in patchers, git ref validation, path traversal checks, credential stripping from error messages, single-quote token escaping in askpass scripts, and Bitbucket pagination origin validation. * chore: add secure-coding skill for input validation guidance * fix: remove double-clone of model repo in entrypoint The entrypoint cloned the model repo itself, then passed both the absolute clone path and --model-repo to the core CLI. The core's resolveModelSource() cloned again, joining paths into an invalid nested path like /tmp/erode-model-XXXXX/tmp/model-repo/likec4. Let the core handle cloning via resolveModelSource() and pass --model-ref so the branch reaches it. * fix: inline route handler exception in dependency-extraction prompt Move the outbound-call exception directly into the "Internal API Changes" ignore section so the AI sees it at decision time, not after classifying handlers as internal. Remove the ineffective standalone IMPORTANT CLARIFICATION section. Add gateway proxy example. Add test to guard against accidental removal. Ref: DRI-21 * feat(core): detect and patch new components from PR analysis Extend the drift analysis pipeline to identify brand-new services and applications introduced by a PR that are not yet in the architecture model. The AI now receives the full component index and relationship list, enabling it to propose newComponents alongside relationships. Key changes: - NewComponentSchema with DSL injection guards (id, kind, name, tags) - Stricter dslIdentifierPattern for relationship/component kind fields - LikeC4 and Structurizr patchers generate component DSL lines - Patchers track and surface validationSkipped when tooling unavailable - PR comment and patch PR body render new components prominently - Stage 4 handles new components alongside relationship patching * fix(core): use absolute path for --patch-local and fix component line attribution - --patch-local was writing to repo-relative path instead of absolute, causing writes to fail when CWD differs from model root - Component DSL line attribution used string matching, which misattributed lines when one component ID was a substring of another (e.g. "api" vs "api-gateway"); now uses positional indexing - Replace execSync with execFileSync to avoid shell injection - Add verbose debug logging in model-source catch blocks - Document --model-ref flag and improve docs voice ---------
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the AI-generated model rewrite pipeline with a hybrid LLM-first + deterministic fallback patcher, adds new component detection from PR diffs, and supports cloning remote model repositories for cross-repo workflows.
What changed
LLM-first patching with deterministic fallback — the patcher tries
provider.patchModel()first for intelligent line placement, validated byquickValidatePatch+ full DSL validation. Falls back to brace-scanning insertion if the LLM is unavailable or fails validation.New component detection — Stage 3 can now propose new components (services, apps) found in the PR diff. The patcher deduplicates against the existing
componentIndexand renders format-specific DSL blocks (LikeC4 block syntax, Structurizr single-line syntax).Remote model repo cloning —
--model-repoclones to a temp dir viagit clone --depth 1 --branch <ref>. New--model-refflag controls the branch/tag. Credentials injected viaGIT_ASKPASS(never visible inpsoutput). SeparateMODEL_REPO_PR_TOKEN/model-repo-tokenaction input for cross-repo auth.--open-pr automode — skips PR creation on regular runs; creates/updates only when a model PR branch already exists. Enables a/erode update-modelcomment-driven workflow.Comment deletion — when re-analysis finds no findings (no violations, no model updates, no new components), any previously posted erode comment is deleted instead of leaving a stale comment.
DSL injection prevention — strict Zod patterns (
safeDslTextPattern,dslIdentifierPattern,componentIdPattern) on all AI-generated output before it reaches the patcher. Prevents newlines, quotes, braces, backticks, etc. in free-text fields.Security hardening —
execSyncreplaced withexecFileSync, ref character validation, path containment checks, credential scrubbing from error messages.New GitHub Action inputs —
model-ref,model-repo-token,openai-api-key, three-valuedopen-pr(true/false/auto).Flag behavior
--patch-modelfalse--patch-local--open-pr truetrue--open-pr false--patch-local)--open-pr auto--model-repo--model-refmain--model-repoTest plan
npm run test)erode analyzewith--patch-model --open-pr autoagainst a repo with an existing model PR branch and verify it updates the PR--model-repopointing to a remote repo and verify clone + patch + PR creation