Skip to content

feat: classify python/go/docker/static app types in streamingDetect (#4137) - #4233

Merged
atomantic merged 4 commits into
mainfrom
claim/issue-4137
Aug 15, 2026
Merged

feat: classify python/go/docker/static app types in streamingDetect (#4137)#4233
atomantic merged 4 commits into
mainfrom
claim/issue-4137

Conversation

@atomantic

@atomantic atomantic commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

streamingDetect.js only ever emitted vite+express / vite / single-node-server / nextjs, the four Apple types, or the initialized default unknown. A Python service, Go binary, Docker stack, or static site all fell through to unknown — and because the standardize gate was the negation of NON_PM2_TYPES (which means "Apple" to its three other consumers), every one of them was still offered PM2 standardization. The standardizer's prompt opens with "You are analyzing a Node.js application", so on a Python repo it doesn't fail — it confidently writes a Node ecosystem config.

This teaches detection to classify the non-Node runtimes and replaces the "everything unrecognized is a PM2 Node app" gate with a predicate that refuses only what it has positively identified as non-Node.

DetectionclassifyNonNodeType(files) maps top-level marker files to python / go / docker / static. It runs only when there is no package.json (any package.json means Node tooling owns the repo, so unknown stays the honest answer) and only when nothing earlier claimed the repo, so an Apple type detected from the repo layout is never overwritten.

Standardization gate — new NON_STANDARDIZABLE_TYPES (= NON_PM2_TYPESNON_NODE_TYPES) and isStandardizable(type) in streamingDetect.js, mirrored into client/src/components/apps/constants.js with parity tests. standardizeRefusalFor() and all three UI surfaces (Add App wizard card, Apps-list row button, app Overview tab button) gate on it.

Type labelsgetAppTypeLabel was a ternary chain whose fallback was '🔨 Xcode', so any unlisted type rendered as an Xcode project. It is now a total map with a raw-type fallback, covering the new types.

Decisions (unattended run)

  • NON_PM2_TYPES is deliberately NOT widened. Its other consumers (appDeployer's deploy gate, cosTaskRoutes' slashdo workflow filter, the client's Xcode-only UI branches) read it as "Apple", and a Python or Go service genuinely can run under PM2. usesPm2() therefore still returns true for the new types; the standardizer gate is a separate predicate. This follows the fix shape in the issue.
  • The gate is a deny list, not an allowlist of Node types. type is a free-form string (appSchema defaults it to express and accepts any value) persisted on the app record, and installs upgrade independently — records in the wild carry unknown, express, and older/custom values. An allowlist would silently withdraw the standardize button from all of them on upgrade, so only a positively identified non-Node type is refused and anything unrecognized keeps the pre-change behavior. This satisfies the issue's "the predicate must stay permissive for unknown" requirement generally rather than case by case.
  • Classification order: language → Node server entry → docker → static. A Python service that also ships a Dockerfile is python (language beats packaging); a package-less repo shipping server.{js,mjs,cjs,ts} or an ecosystem.config.* is a Node app someone containerized, so it outranks both the docker and static rules and falls through to unknown (still standardizable); a compose-only stack is docker; static is the last-resort match.
  • server is the only basename treated as server evidence. index.js, app.js, and main.js are at least as common as browser scripts loaded by an index.html, so counting them would leave real static sites unclassified and still offered the Node standardizer — trading one false negative for the false positive this issue was filed about.
  • /api/standardize/* now standardizes the checked app's own repo. resolveStandardizeTarget preferred a companion repoPath over app.repoPath, so a permitted Node appId paired with another directory would carry a non-Node repo past the refusal — making the gate decorative. No caller sends both (apiSystem.js sends one or the other), so this only closes the hole.
  • Refusal messages stay distinct — Apple types keep the existing "not run under PM2" wording; the new types get "not Node.js projects, so there is no Node ecosystem config to generate".
  • CreateApp.test.jsx's shared fixture used a made-up type: 'node'; it now uses the real single-node-server.

Test plan

  • CI is green on every job, including test (24.x), DB tests and server smoke, Client tests and build, Windows server unit tests, and lint.
  • cd server && npm test — all affected suites pass (streamingDetect, pm2Standardizer, routes/standardize, services/socket, routes/apps/*, services/apps, services/appDeployer, routes/cosTaskRoutes: 306 passed / 1 skipped). Locally the full run also reports Postgres-backed store/route suites failing with … requires PostgreSQL; that is an environment condition, not a regression — the same set fails on main here, and CI's DB job passes.
  • cd client && npm test — 637 files / 7716 tests, all passing.
  • New coverage: classifyNonNodeType per marker, its ordering (language → Node entry → docker → static), every JS/TS server extension, the containerized-Node case, and the ambiguous-browser-script case; isStandardizable across Node / unknown / blank / legacy-custom / non-Node / Apple types plus a guard pinning appSchema's default type; client parity tests for NON_NODE_TYPES and NON_STANDARDIZABLE_TYPES; an explicit assertion that NON_PM2_TYPES was not widened; end-to-end streamDetection runs over temp repos for each new type plus the "package.json present ⇒ stays unknown", "served repo with an index.html stays standardizable", and "Apple type not overwritten" guards; a route test that a companion repoPath cannot redirect the standardizer; standardizeRefusalFor refusals for the four new types; and wizard tests that the standardize card is absent for non-Node types, present for unknown, and that the banner explains why.

Review

Self-review caught that appSchema defaults type to express. Headless claude review returned no findings. Headless codex ran three rounds and every finding is fixed in this branch: the closed allowlist that would have hidden the button for legacy persisted types, index.html beside a server misclassified as static, server entries checked after docker and only matching .js, app.js/main.js wrongly counted as server evidence, and the /api/standardize/* target resolution preferring a companion repoPath over the record it just type-checked.

Closes #4137

@atomantic
atomantic merged commit 8c0cfee into main Aug 15, 2026
6 checks passed
@atomantic
atomantic deleted the claim/issue-4137 branch August 15, 2026 03:27
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.

Classify non-Node app types (python/go/docker/static) in streamingDetect

1 participant