Skip to content

refactor(paths): absorb trimmed-path guards into pathspec.PathSpec (links-va-001-pathspec-drq.1)#203

Merged
brandon-fryslie merged 1 commit into
masterfrom
links-va-001-pathspec-drq.1_pathspec
Jun 10, 2026
Merged

refactor(paths): absorb trimmed-path guards into pathspec.PathSpec (links-va-001-pathspec-drq.1)#203
brandon-fryslie merged 1 commit into
masterfrom
links-va-001-pathspec-drq.1_pathspec

Conversation

@brandon-fryslie

Copy link
Copy Markdown
Collaborator

Implements links-va-001-pathspec-drq.1 (epic links-va-001-pathspec-drq, audit finding F-20). Second ticket in the va-001 absorption family, mirroring the shape piloted by #202.

What

New leaf package internal/pathspec. PathSpec carries a possibly-absent filesystem path as a value: whitespace-trimmed exactly once in the constructor ([LAW:single-enforcer]), absence represented as the zero value, and absence propagating through Or/Join as data rather than per-site guards ([LAW:dataflow-not-control-flow]).

Absorbed sites (old per-site strings.TrimSpace(x) == "" logic deleted, not left beside):

  • internal/config/config.goglobalConfigPath, projectConfigPath (env-override chains become New(env).Or(default)), mergeConfigFile, Load's workspace-root check
  • internal/templates/templates.goGlobalPath, ProjectPath, ActiveOverride (now return/traffic in PathSpec), readOptionalFile, projectTemplatePath, globalTemplatesDir
  • internal/cli/quickstart_eject.go — a 9th pellet the audit missed: planEject re-trimmed GlobalPath's result

Deviations from the epic sketch (per the precedents recorded on this ticket)

  • Dropped valid bool from the sketch's struct — it is derivable from value; two fields that must agree is [LAW:one-source-of-truth] violated inside the type itself.
  • Added Or and Join — both demanded by actual consumers; Join preserves absence (filepath.Join("", x) would invent a relative path from nothing).
  • workspace.normalizeRemoteName excluded. The audit counted it as a site, but a git remote name is not a path — naming it PathSpec would make the type lie ([FRAMING:representation]). It is also already its own single enforcer (one function, two callers): no duplicated variance to absorb. Deleted the dead firstNonEmptyTrimmed helper in that file instead — zero callers, pure residue of this pattern.

Verification

  • go test ./... clean; go vet clean.
  • New contract tests: pathspec (trim, absence, Or, Join propagation) and TestLoadAbsentWorkspaceRootFallsThrough pinning the behavior the deleted guards carried ([LAW:behavior-not-structure]).
  • Old-vs-new binary output byte-identical on quickstart / backlog / queue / show against the live store.
  • gofmt -l delta vs master: empty (the two flagged touched files were already unformatted on master).

…inks-va-001-pathspec-drq.1)

PathSpec carries a possibly-absent filesystem path as a value: trimmed
once at construction ([LAW:single-enforcer]), absence as the zero value,
and absence propagating through Or/Join as data instead of per-site
guards ([LAW:dataflow-not-control-flow]).

Deviations from the epic sketch, per the recorded va-001 precedents:
- Dropped the sketch's 'valid bool' field — derivable from value; two
  fields that can disagree violate [LAW:one-source-of-truth].
- Added Or/Join — both demanded by consumers (env-override chains,
  dir+name joins with absence propagation).
- workspace.normalizeRemoteName excluded: a git remote name is not a
  path ([FRAMING:representation]), and it is already its own single
  enforcer — no duplicated variance there. Deleted the dead
  firstNonEmptyTrimmed helper instead.
- Absorbed a 9th pellet the audit missed: cli planEject re-trimming
  GlobalPath's result.

Verified: go test ./... clean; old-vs-new binary output byte-identical
on quickstart/backlog/queue/show against the live store.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Z.ai Coding Agent Review

Clean refactoring that introduces pathspec.PathSpec to absorb scattered strings.TrimSpace(path) == "" guards into a single authority. The type is well-designed: unexported value field enforces construction through New, zero value represents absence, and Or/Join propagate absence as data rather than re-guarding.

No LAW violations introduced. The new type correctly realizes [LAW:single-enforcer] (trimming once, in New), [LAW:types-are-the-program] (absence is the zero value, not a runtime check), and [LAW:dataflow-not-control-flow] (absence flows through Or/Join as data). Behavioral tests satisfy [LAW:behavior-not-structure].

Pre-existing note: ejectResult.Path and quickstartRefreshItem.Path remain string fields, so the PathSpec guarantee is unwound at the DTO boundary via .String(). This is a reasonable seam choice for JSON-serializable output structs, but if these structs ever gain path-computation logic, they'd benefit from carrying PathSpec directly.

✅ Approved

@brandon-fryslie brandon-fryslie merged commit 46034b5 into master Jun 10, 2026
6 checks passed
@brandon-fryslie brandon-fryslie deleted the links-va-001-pathspec-drq.1_pathspec branch June 10, 2026 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant