Skip to content
| Marketplace
Sign in
Visual Studio Code>Other>Almost Perfect AI ContextNew to Visual Studio Code? Get it now.
Almost Perfect AI Context

Almost Perfect AI Context

Preview

Almost Perfect

| (0) | Free
| Sponsor
Exports the selected code plus the code it calls, the code that calls it, and the config it reads, as one Markdown file for any AI chat.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Almost Perfect AI Context

Beta, Java only. This pre-release supports Java with Spring. Expect rough edges; issues and examples of flows it gets wrong are welcome.

Select code in VS Code, run Export Context for AI, and get one Markdown file to paste into any chatbot that can't see your repositories. The file holds:

  • the selected code
  • the methods it calls, following interfaces into their implementations, across every repository in the workspace
  • the methods that call it
  • the config keys that code reads, with their values from Spring application*.yml files and Kubernetes / Helm files, secrets masked

Methods close to the selection keep their full body; farther ones are cut to a signature. Each class shows only the members on the call path. A token budget shortens or drops the farthest methods first.

How it works

The call walk asks the language server VS Code already runs (for Java: Red Hat's Java extension, jdtls) for the call hierarchy, implementations and document symbols. Everything language-specific lives in a language pack under src/languages/: which files are tests, generated or library code, which config keys code reads and where config lives, and framework links such as Spring events, Kafka topics or Feign clients.

This beta enables the Java pack only. A TypeScript/JavaScript pack exists in the code but is switched off until it has been tried on real projects. Files in other languages, TypeScript included, get the plain call walk without framework links; signature shortening assumes C-family { } syntax.

Use

  1. Open your repositories as one multi-root workspace (File → Add Folder to Workspace…, then save the .code-workspace file). For Maven, a library opened in the same workspace at the version a consumer depends on is resolved straight to its source.
  2. Wait until the Java status bar says the project is ready.
  3. Select code, or just put the cursor inside a method, then right-click and choose Export Context for AI.
    • Cursor in a method: that method is the starting point.
    • Selection inside one method: only the calls made in the selected lines are followed, and the selection is quoted.
    • Selection over several methods or a whole class: each method becomes a starting point.
  4. The file opens beside the editor, is saved to .ai-context/ in the first workspace folder, and is copied to the clipboard.

The first export after VS Code starts can take 15–25 seconds while the Java language server builds its search index; later exports take about a second.

When a repository depends on a different version of a library you have open, jdtls points into the jar. The extension then looks up the class's source by its full name in the workspace and continues from there. The source may not match the jar version exactly; when the jar's version differs from the version in the library's pom.xml, Notes says so.

What it follows, and what it can't see

It follows direct calls, calls through interfaces into their implementations, calls made inside lambdas (shown as the method that contains the lambda), and Spring events: publishEvent(new SomeEvent(...)) leads to every @EventListener, @TransactionalEventListener or @ApplicationModuleListener method that takes SomeEvent. Kafka works the same way: kafkaTemplate.send(topic, ...) leads to every @KafkaListener (or @KafkaHandler under a class-level @KafkaListener) in the workspace whose topic matches, in any repository. The topic can be a string, a constant, or a @Value("${...}") field, and matches when both sides name the same topic, the same config key, a config value that names the topic, or a topicPattern.

HTTP between your own services works through Spring Cloud OpenFeign: a call to a @FeignClient interface method leads to the controller method in any workspace repository that serves the same HTTP method and path, shown as via HTTP `GET /orders/{id}` . The client path is the path part of the client's url, its path, and the method's @GetMapping/@PostMapping/@RequestMapping(method = ...) path; the controller path is the class @RequestMapping plus the method mapping, with or without a configured server.servlet.context-path. {var} segments, * and ** match any segment. Paths can be strings, constants or ${...} placeholders, read from your config files.

Spring AOP advice that runs around a method on the flow is shown under that method, as advised by AuditAspect.audit(ProceedingJoinPoint) : Object [@Around], with the advice method's code. Every @Around, @Before, @After, @AfterReturning and @AfterThrowing method in an @Aspect class in the workspace is read, with its pointcut given as a string, a constant or a concatenation of both; named @Pointcut methods are followed by name, in the same class or qualified, and combined with &&, || and !. A method matches execution(...) and within(...) with *, .. and + wildcards (execution also matches a method declared on an interface the class implements), @annotation(...) on the method, @within(...) and @target(...) on its class, and bean(...) when the bean name comes from @Component, @Service, @Repository, @Controller, @RestController or @Configuration. Annotation and type names can be simple, qualified or bound to an advice parameter. args(...), this(...) and target(...) are checked against the declared parameter and class types, not the runtime ones. cflow, cflowbelow, if() and @args are not checked: advice that depends on them is shown unless the rest of the pointcut rules it out. Private, static and final methods and the aspects themselves are never shown as advised, since a Spring proxy cannot intercept them; a call from inside the same class also skips the proxy at runtime, but the export still shows the advice. Every matching advice is shown, outside apac.maxImplementations.

An exception that a method on the flow throws with throw new OrderNotFoundException(id) or declares in its throws clause is linked to the Spring MVC handler that turns it into the HTTP response, shown under that method as handled by `OrderNotFoundException` GlobalErrors.notFound(OrderNotFoundException) : ResponseEntity<ErrorBody>, with the handler's code. The handler is picked as Spring does: an @ExceptionHandler method in the controller class (or a class it extends) wins, then the first @ControllerAdvice or @RestControllerAdvice class by @Order that applies to the controller and handles the exception. Inside one class the handler mapped to the closest supertype of the exception wins; the supertypes come from the Java language server. Handlers map the classes in @ExceptionHandler(...) (value or exception), or the method's parameter types when the annotation names none. An advice's basePackages (strings or constants), basePackageClasses, assignableTypes and annotations decide which controllers it applies to; a base package that cannot be read keeps the advice. When several advice classes with the same order handle the exception, all are shown, since Spring then picks by bean registration order. When nothing handles the exception and its class carries @ResponseStatus, the thrown constructor is shown as throws PaymentPendingException.PaymentPendingException(String) [@ResponseStatus(HttpStatus.CONFLICT)]. Handlers are linked only for controllers that the flow itself passes through, so export from the controller method; a flow that starts in a service method shows no handlers, even when its callers are controllers.

Enums a method on the flow only references, such as OrderStatus.CANCELLED, are shown with every constant and its arguments, the fields, the constructor and static lookups like fromCode(...); the token budget shortens them first.

A MapStruct mapper shows its interface method with the @Mapping annotations and, under it, the generated implementation marked generated, so implicit same-name mappings and fields left unmapped are visible; fixes belong in the interface annotations. Other generated code is left out.

It can't see what the framework invokes without a call in your source: @PrePersist and other JPA callbacks, @Scheduled triggers, an event published from a variable instead of new SomeEvent(...), a Kafka topic computed by a method call, HTTP calls made other than through a @FeignClient (RestTemplate, WebClient, a web or mobile client), and aspects configured in XML (<aop:config>), inherited from an abstract aspect, or applied to a bean whose name comes from a @Bean method. For exceptions, it does not see ones thrown inside libraries (a repository's EmptyResultDataAccessException, a validation error), handlers matched through the exception's cause or an interface it implements, rethrown variables (throw e), or Kafka error handlers; a throw that a surrounding catch takes still shows its handler. For bug hunting, start from the entry point of a flow (controller method, job, listener), not a small helper at its end.

Your own libraries

Calls are followed into a library's source only when the Java extension has imported that library as a project:

Library setup Followed
Another repository opened in the same workspace, used as a Maven <dependency> Yes
The same, but the app pins an older version (resolved from a jar) Yes, mapped back to the source by class name
A <module> of the repository's parent pom Yes
A standalone Maven project inside the repository folder, not listed in <modules> No: the Java extension does not import nested projects. List it as a <module>, or open it as its own workspace folder from outside the repository

When a flow reaches code that cannot be traced this way, the export's Notes section says which class is missing and where its source is, so the chat assistant knows the flow continues.

Settings

Setting Default What it does
apac.outgoingDepth -1 Hops followed into called code. -1 follows calls until they leave the workspace; library code is never followed. 0 turns it off.
apac.incomingDepth 2 Hops followed back to callers. 0 turns it off.
apac.fullBodyDepth -1 Methods up to this many hops away keep their body. -1 keeps every body and lets apac.maxTokens shorten the farthest first.
apac.maxImplementations 3 Implementations followed per interface or abstract method. Going through an interface does not use up a hop.
apac.includeTests false Follow calls into and from test code (src/test, *Test, *Tests, *IT). Off to save tokens; the method you start from is always kept.
apac.maxNodes 500 Safety cap on methods visited; the export says so when it is reached.
apac.maxTokens 30000 Approximate output budget (characters ÷ 4). Bodies of the farthest methods are shortened first; the call flow always lists every method.
apac.excludePackages JDK, Spring, logging, Jackson, … Class-name prefixes never followed.
apac.configGlobs application* / bootstrap* under src/main/resources Workspace config files searched.
apac.extraConfigPaths [] Extra files or folders searched in the listed order. In user settings they may point anywhere; in workspace settings they must stay inside the workspace folders.
apac.redactKeyPattern password/secret/token/signing/…/*-key Values whose key matches are replaced with ***. User settings only, so a repository can't weaken masking.
apac.output fileAndClipboard file, clipboard, or both.
apac.outputFolder .ai-context Where files are written. Add it to .gitignore.

Config from Kubernetes and Helm

Point extraConfigPaths at your deployment files, lowest precedence first. A repository's own .vscode/settings.json or .code-workspace may only list paths inside the workspace; anything outside, such as a separate config repo or ~/env-config, goes in your user settings. Skipped paths and config files over 2 MB are listed in the export's Notes. ${workspaceFolder}, ${workspaceFolder:<name>} and ~ are expanded:

"apac.extraConfigPaths": [
  "${workspaceFolder:deploy}/helm/orders/values.yaml",
  "${workspaceFolder:deploy}/helm/orders/values-prod.yaml",
  "${workspaceFolder:deploy}/helm/orders/templates",
  "~/work/env-config/orders/prod"
]

Keys are matched the way Spring binds environment variables: orders.max-items also matches ORDERS_MAX_ITEMS / ORDERS_MAXITEMS, whether it sits in a container env: list, a ConfigMap data: block, or Helm values*.yaml. Helm templates aren't valid YAML, so they are searched line by line. Values coming from secretKeyRef show the reference, never the secret.

References are followed up to three steps: a value like ${app.base-url} or ${APP_BASE_URL:default} pulls in that key too, and a Helm template line like value: {{ .Values.springProfile }} pulls in springProfile from every values*.yaml, so you see which profile each environment runs. Lines already listed under an earlier key aren't repeated.

The extension lists every value it finds per key, in file order. It does not compute the effective value (that would mean rendering Helm and knowing which overlays apply); the chatbot gets the full picture and the precedence rule.

Keys are collected from @Value("${…}"), @ConfigurationProperties(prefix = …) and getProperty("…") in the exported code. A @ConfigurationProperties class is only checked when it is itself on the call path, not when it is just a field type.

Before you paste

The output goes to an outside service. Masking works by key name only, so a secret stored under an innocent-looking key, or hard-coded in source, is not caught. Read the file before sending it.

Support

Questions, bug reports and feature requests: the Q & A tab on this extension's Marketplace page, or dev.almostperfect+aicontext@gmail.com.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft