ADR-021: Represent Project Type on PresentationModel as One Display String, Not a Boolean Set¶
| Field | Value |
|---|---|
| Date | 2026-08-06 |
| Status | Accepted |
| Deciders | Holger Zahnleiter |
| Supersedes | — |
| Superseded by | — |
Context and Problem Statement¶
repo init's SUMMARY output shows the generated project's language but not its project type
(library/service/tool/react-frontend/firmware-stm32) — an inconsistency, since both are equally
fundamental facts about what got generated. internal.ProjectDescriptor.ProjectType already
carries this value, but model.NewPresentationModelForTemplatingGenerator reads it only once, to
derive the single IsService boolean, then discards it — PresentationModel has no field a
caller can use to display it.
PresentationModel already has a precedent for representing language this way: four booleans
(IsCppProject/IsGoProject/IsJavaProject/IsTypescriptProject), because template-selection
code throughout internal/templating/{cpp,go,java,typescript} and the .tmpl files themselves
branch on language repeatedly. Should project type follow the same boolean-per-value pattern?
Decision Drivers¶
library_design.md: "Minimize public API surface; expose only what consumers genuinely need."- Every current place that branches on project type —
capability/generator.go's profile lookup, each language'scapabilities.go(goTypeDir,javaTypeDir,cppTypeDir, Makefile-partial selection),adrs.Compute— readsinternal.ProjectTypedirectly offProjectDescriptor, never throughPresentationModel. Unlike language,PresentationModelcurrently has zero consumers of project-type information. - SUMMARY is the only thing that will read this field once added.
Considered Options¶
- Add one derived field,
ProjectType string, populated viaproject.ProjectType.String(). - Add a parallel five-boolean set (
IsLibrary/IsService*/IsCLITool/IsReactFrontend/IsFirmware) mirroring the language pattern (*IsServicealready exists, reused). - Don't touch
PresentationModelat all; derive the display string inline at print time frominternal.ProjectType, passed as an additional parameter toBuildPostGenerationSummary.
Decision Outcome¶
Option 1.
ProjectType string // e.g. "library", "service", "command line tool" (see internal.ProjectType.String())
populated in NewPresentationModelForTemplatingGenerator via project.ProjectType.String().
Positive Consequences¶
- Matches how every other print-ready SUMMARY value already lives on
PresentationModel(ProjectName,TechnicalArtifactName, ...) — one consistent way of threading data intoBuildPostGenerationSummary, not two. - No unused surface: the field has exactly the one consumer that motivated adding it.
- Verified directly by
TestNewPresentationModelForTemplatingGenerator_ProjectType(internal/templating/model/presentation_model_test.go), a table-driven test over all fiveinternal.ProjectTypevalues — proving the single field correctly covers every case a boolean-per-value scheme would otherwise have needed to cover individually.
Negative Consequences / Risks¶
- If a future template genuinely needs to branch on project type the way
.tmplfiles branch on language today, this field alone won't support that (a string isn't a template-friendly conditional the way a bool is) — at that point, per-type booleans would need to be added alongside it. No such need exists today; adding them now would be speculative.
Rejected alternatives¶
- Option 2 (five booleans) would add public struct surface with no current caller — exactly
the unused-API-surface
library_design.mdwarns against. The language booleans earn their keep because template code actually branches on them; project type has no such caller yet. - Option 3 (no model field, pass
internal.ProjectTypestraight through) was rejected because it introduces a second, inconsistent way of getting data intoBuildPostGenerationSummary— every other SUMMARY value already flows throughPresentationModel, andinternal.ProjectTypeis a domain type from theinternalpackage rather than a display-ready string, sointernal/templatingwould gain a direct dependency oninternal.ProjectType'sString()method at the call site instead of the model owning that translation once.
Links¶
internal/project_type.go—ProjectType.String(), the source of the display text (itsPROJECT_TYPE_TOOLcase was corrected from"comand line tool"to"command line tool"as part of this change, since it's now user-facing for the first time).internal/templating/model/presentation_model.go— the newProjectTypefield.internal/templating/post_generation_instructions.go—BuildPostGenerationSummary's use ofm.ProjectTypein the SUMMARY headline.