Audit the public facade boundary

This commit is contained in:
2026-09-12 16:26:44 +00:00
parent 7f46c947dd
commit 5dc7e18d83
9 changed files with 216 additions and 136 deletions

View File

@@ -1,11 +1,11 @@
{
"schema_version": 2,
"commit": "550f92d7c6f197d7f4996919a418d9ca0174eb07",
"indexed_at": "2026-09-12T16:21:16Z",
"commit": "7f46c947dd56f437077df475ba5bba8fd864e2e1",
"indexed_at": "2026-09-12T16:26:42Z",
"project": "home-eric-Workspace-ConfigKit",
"nodes": 1625,
"edges": 7835,
"edges": 7824,
"original_size": 8126464,
"compressed_size": 1330563,
"compressed_size": 1329933,
"compression_level": 3
}

Binary file not shown.

View File

@@ -2,7 +2,7 @@
## Status
Accepted. This records a design decision, not implementation completion.
Accepted and implemented on 2026-09-12.
## Date
@@ -10,32 +10,31 @@ Accepted. This records a design decision, not implementation completion.
## Context
The root `configkit` package currently combines the supported consumer API with
the loading, decoding, inspection, and rendering machinery. That makes ordinary
consumer navigation harder and risks exposing implementation details through
The root `configkit` package formerly combined the supported consumer API with
loading, decoding, inspection, and rendering machinery. That made ordinary
consumer navigation harder and risked exposing implementation details through
future public declarations. Moving files alone would not preserve the identity,
method sets, callbacks, ownership rules, or reflection-sensitive behavior of
the root's public types.
method sets, callbacks, ownership rules, or reflection-sensitive behavior of the
root's public types.
## Decision
Keep `configkit` as the sole supported consumer import path and keep all public
named types defined there. When lifecycle machinery is extracted, it will live
in a cohesive `internal/engine`; the root facade may hold private engine state
and forward deliberately selected methods. The dependency direction is root to
engine and its supporting internal packages. Internal production packages do
not import the root package.
named types defined there. Lifecycle machinery lives in a cohesive
`internal/engine`; the root facade holds private engine state and forwards
deliberately selected methods. The dependency direction is root to engine and
its supporting internal packages. Internal production packages do not import the
root package.
The facade will use root-owned wrappers and explicit private conversions. It
will not use public aliases to engine types, anonymous embedding that promotes
engine methods, public engine types in signatures or fields, or public bridge
methods. Existing document and YAML boundaries remain intact.
The migration may use a temporary internal candidate facade while the unchanged
root remains the only production entry point. That duplication is a bounded
The migration used a temporary internal candidate facade while the unchanged root
remained the only production entry point. That duplication was a bounded
relocation mechanism, not a second supported package, feature path, or runtime
selection mechanism. The final cutover removes the temporary candidate and old
root implementation together.
selection mechanism; the final cutover removed it and the old root implementation.
The [architecture policy](../policy/architecture.md#public-api-and-dependency-direction)
owns the binding boundary. The [public-facade roadmap](../roadmap/public.md)

View File

@@ -98,15 +98,15 @@ code changes. Use the component overview to find the corresponding implementatio
| Task | Behavioral test owners | Focused command |
| --- | --- | --- |
| YAML syntax, diagnostics, parser limits | [Adapter tests](../internal/yaml/parse_test.go), [source translation](../internal/engine/parse_test.go) | `go test ./internal/yaml` and `go test . -run '^TestParseSource'` |
| File/reader access, imports, globs, containment | [Sources](../internal/engine/source_test.go), [FIFO](../internal/engine/source_fifo_test.go), [imports](../internal/engine/imports_test.go), [globs](../internal/engine/glob_test.go) | `go test . -run 'Source\|Import\|Glob\|Filesystem\|Nonregular\|RegularFile'` |
| Composition, profiles, CLI precedence | [Composition](../internal/engine/compose_test.go), [overlay](../internal/engine/overlay_test.go), [profiles](../internal/engine/profile_test.go), [overrides](../internal/engine/override_test.go) | `go test . -run 'Compose\|Overlay\|Profile\|Override'` |
| Schema, defaults, projection, cloning | [Schema](../internal/engine/schema_test.go), [decoding](../internal/engine/decode_test.go), [projection](../internal/engine/projection_test.go) | `go test . -run 'Schema\|Decode\|Projection\|Clone\|Inline\|CompareDocument'` |
| Lifecycle, resolution, validation, callback ownership | [Loading](../internal/engine/load_test.go), [resolution](../internal/engine/resolution_test.go), [validation](../internal/engine/validation_test.go), [ownership](../internal/engine/callback_ownership_test.go), [independence](../internal/engine/independence_test.go) | `go test . -run 'Load\|Resolution\|Validation\|Validator\|Hook\|Callback\|Parallel'` |
| Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test . -run 'Path\|Metadata\|Provenance\|Queries'` |
| Secrets, bootstrap, redaction, explanation | [References](../internal/engine/secret_reference_test.go), [bootstrap](../internal/engine/secrets_test.go), [rendering](../internal/engine/render_test.go), [explanation](../internal/engine/explain_test.go) | `go test . -run 'Secret\|Bootstrap\|Effective\|Explain\|Explanation\|History\|Sensitivity'` |
| YAML syntax, diagnostics, parser limits | [Adapter tests](../internal/yaml/parse_test.go), [source translation](../internal/engine/parse_test.go) | `go test ./internal/yaml` and `go test ./internal/engine -run '^TestParseSource'` |
| File/reader access, imports, globs, containment | [Sources](../internal/engine/source_test.go), [FIFO](../internal/engine/source_fifo_test.go), [imports](../internal/engine/imports_test.go), [globs](../internal/engine/glob_test.go) | `go test ./internal/engine -run 'Source\|Import\|Glob\|Filesystem\|Nonregular\|RegularFile'` |
| Composition, profiles, CLI precedence | [Composition](../internal/engine/compose_test.go), [overlay](../internal/engine/overlay_test.go), [profiles](../internal/engine/profile_test.go), [overrides](../internal/engine/override_test.go) | `go test ./internal/engine -run 'Compose\|Overlay\|Profile\|Override'` |
| Schema, defaults, projection, cloning | [Schema](../internal/engine/schema_test.go), [decoding](../internal/engine/decode_test.go), [projection](../internal/engine/projection_test.go) | `go test ./internal/engine -run 'Schema\|Decode\|Projection\|Clone\|Inline\|CompareDocument'` |
| Lifecycle, resolution, validation, callback ownership | [Loading](../internal/engine/load_test.go), [resolution](../internal/engine/resolution_test.go), [validation](../internal/engine/validation_test.go), [ownership](../internal/engine/callback_ownership_test.go), [independence](../internal/engine/independence_test.go) | `go test ./internal/engine -run 'Load\|Resolution\|Validation\|Validator\|Hook\|Callback\|Parallel'` |
| Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test ./internal/engine -run 'Path\|Metadata\|Provenance\|Queries'` |
| Secrets, bootstrap, redaction, explanation | [References](../internal/engine/secret_reference_test.go), [bootstrap](../internal/engine/secrets_test.go), [rendering](../internal/engine/render_test.go), [explanation](../internal/engine/explain_test.go) | `go test ./internal/engine -run 'Secret\|Bootstrap\|Effective\|Explain\|Explanation\|History\|Sensitivity'` |
| Consumer examples and migration fixtures | [Consumer tests](../examples/consumers/consumers_test.go), [API examples](../example_test.go) | `go test ./examples/consumers` and `go test . -run '^Example'` |
| Documentation links and dependency isolation | [Link checks](../documentation_test.go), [import boundary](../import_boundary_test.go) | `go test . -run '^(TestLocalDocumentationLinks\|TestDependencyImportBoundary)$'` |
| Documentation links and dependency isolation | [Link checks](../documentation_test.go), [import boundary](../import_boundary_test.go) | `go test . -run 'TestLocalDocumentationLinks|TestDependencyImportBoundary|TestPublicFacadeBoundary'` |
| Check-command behavior | [Shell regression](../release_check_test.go) | `go test . -run '^TestCheckScripts'` |
## Fuzz validation
@@ -115,7 +115,20 @@ Run the bounded fuzz targets independently from the repository root. These exact
commands keep each campaign short and constrain concurrent workers:
```sh
go test . -run='^$' -fuzz='^FuzzParsePath$' -fuzztime=30s -parallel=2
go test ./internal/engine -run='^$' -fuzz='^FuzzParsePath$' -fuzztime=30s -parallel=2
go test ./internal/yaml -run='^$' -fuzz='^FuzzParseDocument$' -fuzztime=30s -parallel=2
go test . -run='^$' -fuzz='^FuzzCompositionAndOverlay$' -fuzztime=30s -parallel=2
go test ./internal/engine -run='^$' -fuzz='^FuzzCompositionAndOverlay$' -fuzztime=30s -parallel=2
```
` |
| Check-command behavior | [Shell regression](../release_check_test.go) | `go test . -run '^TestCheckScripts'` |
## Fuzz validation
Run the bounded fuzz targets independently from the repository root. These exact
commands keep each campaign short and constrain concurrent workers:
```sh
go test ./internal/engine -run='^$' -fuzz='^FuzzParsePath$' -fuzztime=30s -parallel=2
go test ./internal/yaml -run='^$' -fuzz='^FuzzParseDocument$' -fuzztime=30s -parallel=2
go test ./internal/engine -run='^$' -fuzz='^FuzzCompositionAndOverlay$' -fuzztime=30s -parallel=2
```

View File

@@ -1,94 +1,39 @@
# Internal Component Overview
ConfigKit exposes the root `configkit` package and keeps its document and YAML
representations private. The implemented components and their owners are:
ConfigKit exposes one supported root `configkit` package. The root owns public
named types, declaration comments, constructors, and explicit conversion/wrapper
code. It delegates loading and frozen inspection to `internal/engine` through
private state; public signatures and fields do not expose engine types.
- a temporary `internal/engine` relocation candidate containing a mechanical
copy of the root loading, decoding, resolution, provenance, inspection, and
rendering mechanisms with their behavior tests; it imports no root code and
is not yet used by the root package or consumers;
- immutable semantic `Path` construction and strict JSON Pointer parsing;
- detached effective presence/origin and chronological history queries in
`metadata.go`, with provenance and diagnostic record types;
- canonical, control-safe diagnostic rendering and structured `LoadError` access;
- opaque file, byte, reader, and no-input source construction in `source.go`,
with shared regular-file checks and bounded read mechanics in `source_file.go`;
- a parser-independent ordered tree in `internal/document`, with root-private
semantic lookup, indexing, and value traversal in `document_walk.go`;
- the sole upstream YAML integration in `internal/yaml`, which translates validated
input into that owned tree and exposes no parser types outside its boundary;
`parse.go` owns shared translation into detached, ordered public diagnostics;
- a private strict composer in `compose.go` that joins only disjoint document-tree
definitions, retains ordered claims and composite mapping origins, reports every
conflicting location, and never aliases an input tree;
- fixed selected-profile loading in `profile.go` and recursive, non-aliasing overlay
with displacement records in `overlay.go`; overlay records retain origins,
while value explanation uses separate history applications;
- CLI override validation/preparation in `override.go`, parser-neutral values in
`override_value.go`, and synthetic-source application in `override_apply.go`,
sharing overlay, source, byte, node, and depth policies;
- a per-operation private reflection descriptor graph in `schema.go` for supported
types, YAML field binding, inlining, secret tags, recursion, and semantic paths;
- descriptor-driven canonical projection in `projection.go`, detached typed
cloning with explicit secret-state modes in `clone.go`, shared reflection and
cycle/limit state in `value_operation.go`, and configuration-value comparison
in `document_compare.go`; validation separately audits sensitivity and secrets;
- strict schema-driven decoding over detached defaults in `decode.go`, including
mapping/sequence semantics, null and numeric policy, safe codec errors, presence,
baseline/final trees, and initial default/input origins;
- public lifecycle coordination in `load.go`, option selection in `options.go`,
input assembly in `load_input.go`, typed phases/publication in `load_result.go`,
bounded canonical import DFS in `imports.go`, filesystem context/containment in
`filesystem.go`, directive parsing/restrictions in `directives.go`,
pattern-directed glob expansion in `glob.go`, schema-version checking and report
contexts in `validation.go`, and layered metadata/history assembly in
`load_metadata.go`;
- recorded application defaulting and normalization in `resolution.go`, including
callback-scoped fixed entry metadata/source queries, explicitness queries, helper
verification, structural mutation audit, coalesced intent, private before/after
snapshots, and resolution metadata integration;
- post-resolution schema-aware report collection and validator mutation detection in
`validation.go`, completing the public callback lifecycle after composed input;
- opaque typed environment references and per-load name resolution in
`secret_reference.go`, including credential-free projection/cloning and private
validator-state auditing;
- explicit bounded startup environment bootstrap and safe partial reporting in
`secrets.go`;
- fixed-point tag/predicate/dependency sensitivity classification in `secret.go`;
- canonical effective output in `render.go`, using frozen-snapshot redaction
shared with explanation in `snapshot.go`;
- detached public explanation records and coordination in `explain.go`, immutable
input applications/replay and retained-node budgeting in `history.go`, shared
safe snapshot projection in `snapshot.go`, and deterministic human output in
`explain_render.go`; the only upstream dumper integration remains isolated in
`internal/yaml/write.go`;
- a separate maintained `examples/consumers` package with four sanitized consumer
shapes, fixtures, domain validators, and runtime-boundary functions; and
- an everyday single-toolchain check in `scripts/check.sh` and a stricter
source-candidate checker in `scripts/release-check.sh`, with local
documentation-link and public import-boundary gates owned by root-package tests.
Implemented component owners:
Dependency direction remains inward: the public lifecycle coordinates private
source, document, schema, and inspection helpers. Semantic paths stay in the root
package; `internal/document` owns only representation and cloning and never imports
the root package. Only `internal/yaml` imports the upstream YAML package. Maintained
consumer examples use the public package and standard library.
- `internal/engine` owns source acquisition, composition, schema inspection,
decoding, lifecycle coordination, callback auditing, provenance/history,
secret resolution and bootstrap, and safe inspection rendering. It never
imports the root package.
- Root `Source`, `Path`, `Secret`, records, lifecycle callbacks, results, and
bootstrap reports are root-defined wrappers or records. Callback wrappers pass
only live engine context during a callback; `Result.Config` is detached and its
private inspection remains frozen independently.
- Root conversion files explicitly cross the path, record, callback, secret, and
error boundaries. There are no public aliases, anonymous engine embeddings, or
public engine bridge methods.
- `internal/document` owns the parser-independent ordered tree and cloning.
`internal/yaml` is the sole upstream YAML adapter and parser/dumper boundary.
- Engine mechanism tests own source, composition, decoding, callback, provenance,
secret, history, rendering, and bootstrap behavior. Root tests own public API
compatibility, facade integration, documentation links, and dependency/public
boundary checks. Consumer examples remain under `examples/consumers`.
The [configuration contract](../config.md) owns implemented input, schema, decoding,
loading lifecycle, path imports, composition, profile/CLI overlay, version checking,
recorded resolution, typed secret references/bootstrap, semantic validation,
metadata, secret classification, redacted effective rendering, historical
explanation, and ownership behavior.
The [consumer adoption guide](../consumers/README.md) owns current application-fit
and migration guidance.
Dependency direction is root to engine to document/YAML support. The root may
import only `internal/engine` among project internal production packages; engine
and its supporting packages do not import root. The root boundary test enforces
these constraints and rejects exported aliases, fields, or signatures involving
internal types.
The source-extraction inventory records the application foundations used by the
implementation:
- [Extraction inventory](extraction.md)
The [architecture policy](../policy/architecture.md) owns binding boundaries and
invariants. The [deferred scope](../roadmap/deferred.md) records deliberate
exclusions. See the
[development guide](../development.md) for contributor orientation and validation
and the [release procedure](../release.md) for source-candidate gates.
The [configuration contract](../config.md) owns implemented YAML and lifecycle
semantics. The [consumer adoption guide](../consumers/README.md) owns application
guidance. The [architecture policy](../policy/architecture.md) owns contributor
boundaries, and [ADR 0006](../adr/0006-public-facade.md) records the facade
decision. See the [development guide](../development.md) for validation commands
and [release procedure](../release.md) for source-candidate gates.

View File

@@ -83,10 +83,10 @@ types or acquire internal methods through anonymous embedding. Root production
code may depend inward on a cohesive engine and its supporting internal packages,
but internal production packages must not import the root. Explicit forwarding
and local conversion are acceptable where they preserve type identity,
ownership, callback lifetime, and inspection behavior. The accepted
root-wrapper boundary and its migration rationale are recorded in
[ADR 0006](../adr/0006-public-facade.md); its implementation remains tracked by
the public-facade roadmap.
ownership, callback lifetime, and inspection behavior. The accepted root-wrapper
boundary and its migration rationale are recorded in
[ADR 0006](../adr/0006-public-facade.md); completed implementation evidence is
recorded in the public-facade roadmap.
Separate input access, document processing, composition, decoding, metadata, and
rendering responsibilities without prescribing an interface or package for each.

View File

@@ -1,8 +1,8 @@
# Public facade: implementation plan
Status: not started. Baseline: `28e7fc5f690b36c60e4d03586afcff00fb1dee90`.
The [feature roadmap](public.md) owns the target architecture and user intent.
This plan owns implementation decisions, ordered work, validation, and handoffs.
Status: complete (2026-09-12). Baseline: `28e7fc5f690b36c60e4d03586afcff00fb1dee90`.
The [feature roadmap](public.md) owns the implemented architecture and user intent.
This completed plan records implementation decisions, ordered work, validation, and handoffs.
Each numbered stage is sized for one gpt-5.6-terra coding-agent assignment;
no stage requires subagents or changes to downstream repositories.
@@ -508,7 +508,7 @@ boundary and brings documentation in line with the completed architecture.
## Stage 9 — Audit the public boundary and finalize documentation
Status: not started.
Status: complete (2026-09-12).
1. Compare the final complete exported API against the baseline. Audit public
signatures, fields, aliases, embedded types, method sets, and error returns
@@ -538,6 +538,29 @@ supported baseline and newer Go toolchains recorded in the release procedure.
Record actual versions/platforms; do not treat Linux tests or cross-builds as
native macOS release evidence. Do not publish or select a version in this stage.
### Completion
Audited the final root facade against the established public surface and added a
root AST boundary check for exported aliases, fields, and signatures involving
internal types. The dependency boundary check now also rejects root production
imports that bypass the engine and internal production imports of the root.
The completed root retains only facade wrappers, records, conversions, and
entry-point delegation; engine mechanism tests own lifecycle behavior.
Updated the architecture policy, ADR status, component overview, development
test/fuzz navigation, and public-facade roadmap to describe the implemented
layout. The roadmap is now a completed implementation record rather than a
future plan. Root facade integration and engine tests cover source ownership,
exact-type secret binding, callback activity/audit, structured errors, detached
configuration versus frozen inspection, and resolution entry snapshots.
Linux validation passed with Go 1.25.5 and Go 1.26.7 through
`CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5 CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7 ./scripts/release-check.sh`.
It also passed the everyday check, full race suite, consumer examples, and local
documentation/import/public-boundary tests. This is not native macOS evidence
and no release version, tag, or publication was selected. There were no
deviations or remaining risks.
## Open Questions
None requiring a user decision were identified. Wrappers, public type ownership,

View File

@@ -1,8 +1,8 @@
# Public facade and internal implementation
Status: agreed direction; not implemented. This roadmap defines the desired end
state and user intent. The [implementation plan](implementation.md) owns the
ordered work, concrete adapter design, validation commands, and handoffs.
Status: implemented on 2026-09-12. This roadmap records the intended architecture
and user intent; the completed [implementation plan](implementation.md) records
the ordered work, validation, and handoffs.
## Intent
@@ -31,13 +31,13 @@ design. Existing [configuration contracts](../config.md) and exported Go
declaration comments remain authoritative. The user-approved wrapper boundary is
a contributor design choice, not a strengthening of consumer behavior contracts.
## Current architecture
## Implemented architecture
The root currently owns both public declarations and most private implementation:
source acquisition, imports, composition and overlays, reflection descriptors,
decoding, resolution and validation coordination, provenance, history, secret
handling, and rendering. The [component overview](../internal/overview.md) owns
the detailed inventory.
The root owns public declarations, records, wrappers, and deliberate forwarding.
`internal/engine` owns source acquisition, imports, composition and overlays,
reflection descriptors, decoding, resolution and validation coordination,
provenance, history, secret handling, and rendering. The
[component overview](../internal/overview.md) owns the detailed inventory.
Two established internal boundaries already exist: `internal/document` owns the
parser-independent tree, and `internal/yaml` owns upstream YAML integration.
@@ -49,7 +49,7 @@ callback state; `Path` holds semantic segments; and `Secret` holds private
reference and resolved-value state. Moving implementation requires deliberate
type ownership, not just file relocation.
## Target architecture
## Architecture
### Public root package
@@ -140,7 +140,7 @@ need to change an established contract requires a separate explicit decision.
Version selection and publication remain governed by the
[release policy](../policy/release.md), not by this roadmap.
## Completion criteria
## Completion evidence
Completion means the root is a legible public surface, substantive plumbing is
internally owned, consumers require no migration, and representative

View File

@@ -1,6 +1,7 @@
package configkit
import (
"go/ast"
"go/parser"
"go/token"
"io/fs"
@@ -8,6 +9,7 @@ import (
"strconv"
"strings"
"testing"
"unicode"
)
func TestDependencyImportBoundary(t *testing.T) {
@@ -41,6 +43,15 @@ func TestDependencyImportBoundary(t *testing.T) {
if strings.HasPrefix(importPath, "github.com/google/go-cmp/") && !strings.HasSuffix(path, "_test.go") {
t.Errorf("%s imports test-only dependency %s", path, importPath)
}
production := !strings.HasSuffix(path, "_test.go")
rootProduction := production && filepath.Dir(path) == "."
internalProduction := production && strings.HasPrefix(filepath.ToSlash(path), "internal/")
if rootProduction && strings.HasPrefix(importPath, "gitea.maximumdirect.net/eric/ConfigKit/internal/") && importPath != "gitea.maximumdirect.net/eric/ConfigKit/internal/engine" {
t.Errorf("%s bypasses the engine boundary through %s", path, importPath)
}
if internalProduction && importPath == "gitea.maximumdirect.net/eric/ConfigKit" {
t.Errorf("%s imports the public root package", path)
}
}
return nil
})
@@ -48,3 +59,92 @@ func TestDependencyImportBoundary(t *testing.T) {
t.Fatal(err)
}
}
func TestPublicFacadeBoundary(t *testing.T) {
t.Parallel()
entries, err := filepath.Glob("*.go")
if err != nil {
t.Fatal(err)
}
for _, path := range entries {
if strings.HasSuffix(path, "_test.go") {
continue
}
file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
if err != nil {
t.Fatal(err)
}
imports := importedPaths(file)
for _, declaration := range file.Decls {
switch declaration := declaration.(type) {
case *ast.FuncDecl:
if exportedIdentifier(declaration.Name.Name) && usesInternalType(declaration.Type, imports) {
t.Errorf("%s exposes an internal type in its signature", declaration.Name)
}
case *ast.GenDecl:
for _, specification := range declaration.Specs {
typeSpec, ok := specification.(*ast.TypeSpec)
if !ok || !exportedIdentifier(typeSpec.Name.Name) {
continue
}
if typeSpec.Assign.IsValid() {
t.Errorf("%s is an exported type alias", typeSpec.Name)
}
structure, ok := typeSpec.Type.(*ast.StructType)
if !ok {
continue
}
for _, field := range structure.Fields.List {
exposed := len(field.Names) == 0
for _, name := range field.Names {
exposed = exposed || exportedIdentifier(name.Name)
}
if exposed && usesInternalType(field.Type, imports) {
t.Errorf("%s exposes an internal type through a field", typeSpec.Name)
}
}
}
}
}
}
}
func importedPaths(file *ast.File) map[string]string {
paths := make(map[string]string, len(file.Imports))
for _, imported := range file.Imports {
path, err := strconv.Unquote(imported.Path.Value)
if err != nil {
continue
}
name := filepath.Base(path)
if imported.Name != nil {
name = imported.Name.Name
}
paths[name] = path
}
return paths
}
func usesInternalType(expression ast.Expr, imports map[string]string) bool {
internal := false
ast.Inspect(expression, func(node ast.Node) bool {
selector, ok := node.(*ast.SelectorExpr)
if !ok {
return true
}
packageName, ok := selector.X.(*ast.Ident)
if ok && strings.Contains(imports[packageName.Name], "/internal/") {
internal = true
}
return !internal
})
return internal
}
func exportedIdentifier(name string) bool {
for _, character := range name {
return unicode.IsUpper(character)
}
return false
}