fix(cli): stop selecting missing comm_health workflow template (#4665)

The communication archetype still listed workflows/comm_health.go.tmpl
after that file was never added to the embed, so every generate against
a communication spec warned and skipped it. Drop the dead selector and
keep selected workflow/insight templates locked to files that exist.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Trevin Chow <tmchow@users.noreply.github.com>
This commit is contained in:
Trevin Chow
2026-09-10 05:32:59 -07:00
committed by GitHub
parent 0878fb0d9e
commit ffbde9923f
4 changed files with 92 additions and 7 deletions
+2 -2
View File
@@ -377,7 +377,7 @@ Table stakes features (from the absorb gate). Every feature the top competitor h
Data layer (high-gravity entities). Domain-specific SQLite tables with proper columns (not JSON blobs), FTS5 full-text search, incremental sync with cursor tracking, `sql` command for raw queries, domain-specific `UpsertX()` and `SearchX()` methods.
Workflow commands (from archetype): `stale`, `orphans`, `load`, `channel-health`, `reconcile`, etc.
Workflow commands (from archetype): `stale`, `orphans`, `load`, etc.
Insight commands (Rung 5): `health` (composite score), `similar` (duplicate detection), `trends`, `bottleneck`, `forecast`, `patterns`.
@@ -390,7 +390,7 @@ The profiler classifies every API into a domain archetype and auto-generates the
| Archetype | Detected by | Auto-generated commands |
|-----------|------------|------------------------|
| Project Management | issue/task/ticket resources, assignee fields, priority levels | `stale`, `orphans`, `load`, `health`, `similar` |
| Communication | message/channel/thread resources, threading fields | `channel-health`, `message-stats`, `health`, `similar` |
| Communication | message/channel/thread resources, threading fields | `health`, `similar` |
| Payments | charge/payment/invoice resources, amount/currency fields | `reconcile`, `revenue`, `health`, `similar` |
| Infrastructure | server/deploy/instance resources | `health`, `similar` |
| Content | document/page/block resources | `health`, `similar` |
+1 -1
View File
@@ -2655,7 +2655,7 @@ func archetypePlaybook(arch profiler.DomainArchetype) []PlaybookEntry {
case profiler.ArchetypeCommunication:
return []PlaybookEntry{
{Topic: "Message search", Insight: "Use the search tool on synced data rather than paginating through message history. Message APIs often have aggressive rate limits."},
{Topic: "Channel health", Insight: "When analyzing channel activity, use the channel-health command or sql aggregation on synced messages. Don't iterate individual messages via API."},
{Topic: "Channel health", Insight: "When analyzing channel activity, use sql aggregation on synced messages. Don't iterate individual messages via API."},
}
case profiler.ArchetypePayments:
return []PlaybookEntry{
-4
View File
@@ -140,10 +140,6 @@ func SelectVisionTemplates(plan *vision.VisionaryPlan) VisionTemplateSet {
"workflows/pm_orphans.go.tmpl",
"workflows/pm_load.go.tmpl",
}
case "communication":
set.Workflows = []string{
"workflows/comm_health.go.tmpl",
}
}
// Invariant: a store without sync is useless — sync populates the store.
@@ -0,0 +1,89 @@
package generator
import (
"os"
"path"
"path/filepath"
"testing"
"github.com/mvanhorn/cli-printing-press/v4/internal/naming"
"github.com/mvanhorn/cli-printing-press/v4/internal/profiler"
"github.com/mvanhorn/cli-printing-press/v4/internal/spec"
"github.com/mvanhorn/cli-printing-press/v4/internal/vision"
"github.com/stretchr/testify/require"
)
func TestSelectVisionTemplatesSelectedFilesExistInEmbed(t *testing.T) {
t.Parallel()
archetypes := []string{
string(profiler.ArchetypeProjectMgmt),
string(profiler.ArchetypeCommunication),
string(profiler.ArchetypePayments),
string(profiler.ArchetypeInfrastructure),
string(profiler.ArchetypeContent),
string(profiler.ArchetypeCRM),
string(profiler.ArchetypeDeveloperPlatform),
"",
}
insight := vision.NonObviousInsight{
InsightFrame: "a local query surface",
Implications: []string{"sync then sql"},
}
for _, arch := range archetypes {
t.Run(arch, func(t *testing.T) {
t.Parallel()
set := SelectVisionTemplates(&vision.VisionaryPlan{
Domain: vision.DomainInfo{Archetype: arch},
Insight: insight,
})
selected := append(append([]string{}, set.Workflows...), set.Insights...)
for _, tmpl := range selected {
_, err := templateFS.ReadFile(path.Join("templates", tmpl))
require.NoError(t, err, "selected template %s for archetype %q must exist in the embed", tmpl, arch)
require.NotEmpty(t, commandConstructorForTemplate(tmpl),
"selected template %s for archetype %q must have a command constructor", tmpl, arch)
}
})
}
}
func TestGenerateCommunicationArchetypeDoesNotWarnOnMissingCommHealth(t *testing.T) {
apiSpec := communicationSpec("commhealthwarn")
profile := profiler.Profile(apiSpec)
require.Equal(t, profiler.ArchetypeCommunication, profile.Domain.Archetype)
outputDir := filepath.Join(t.TempDir(), naming.CLI(apiSpec.Name))
stderr, err := captureNovelFeatureStderr(t, func() error {
return New(apiSpec, outputDir).Generate()
})
require.NoError(t, err)
require.NotContains(t, stderr, "comm_health")
require.NotContains(t, stderr, "skipping workflow template")
_, statErr := os.Stat(filepath.Join(outputDir, "internal", "cli", "comm_health.go"))
require.ErrorIs(t, statErr, os.ErrNotExist)
skillSrc := readGeneratedFile(t, outputDir, "SKILL.md")
require.NotContains(t, skillSrc, "channel-health")
}
func communicationSpec(name string) *spec.APISpec {
apiSpec := minimalSpec(name)
apiSpec.Resources = map[string]spec.Resource{
"messages": {
Description: "Channel messages",
Endpoints: map[string]spec.Endpoint{
"list": {Method: "GET", Path: "/messages", Description: "List messages"},
},
},
"channels": {
Description: "Chat channels",
Endpoints: map[string]spec.Endpoint{
"list": {Method: "GET", Path: "/channels", Description: "List channels"},
},
},
}
return apiSpec
}