feat: classify python/go/docker/static app types in streamingDetect (#4137) - #4233
Merged
Conversation
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
streamingDetect.jsonly ever emittedvite+express/vite/single-node-server/nextjs, the four Apple types, or the initialized defaultunknown. A Python service, Go binary, Docker stack, or static site all fell through tounknown— and because the standardize gate was the negation ofNON_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.
Detection —
classifyNonNodeType(files)maps top-level marker files topython/go/docker/static. It runs only when there is nopackage.json(anypackage.jsonmeans Node tooling owns the repo, sounknownstays 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_TYPES∪NON_NODE_TYPES) andisStandardizable(type)instreamingDetect.js, mirrored intoclient/src/components/apps/constants.jswith parity tests.standardizeRefusalFor()and all three UI surfaces (Add App wizard card, Apps-list row button, app Overview tab button) gate on it.Type labels —
getAppTypeLabelwas 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_TYPESis 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 returnstruefor the new types; the standardizer gate is a separate predicate. This follows the fix shape in the issue.typeis a free-form string (appSchemadefaults it toexpressand accepts any value) persisted on the app record, and installs upgrade independently — records in the wild carryunknown,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 forunknown" requirement generally rather than case by case.Dockerfileispython(language beats packaging); a package-less repo shippingserver.{js,mjs,cjs,ts}or anecosystem.config.*is a Node app someone containerized, so it outranks both the docker and static rules and falls through tounknown(still standardizable); a compose-only stack isdocker;staticis the last-resort match.serveris the only basename treated as server evidence.index.js,app.js, andmain.jsare at least as common as browser scripts loaded by anindex.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.resolveStandardizeTargetpreferred a companionrepoPathoverapp.repoPath, so a permitted NodeappIdpaired with another directory would carry a non-Node repo past the refusal — making the gate decorative. No caller sends both (apiSystem.jssends one or the other), so this only closes the hole.CreateApp.test.jsx's shared fixture used a made-uptype: 'node'; it now uses the realsingle-node-server.Test plan
test (24.x),DB tests and server smoke,Client tests and build,Windows server unit tests, andlint.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 onmainhere, and CI's DB job passes.cd client && npm test— 637 files / 7716 tests, all passing.classifyNonNodeTypeper marker, its ordering (language → Node entry → docker → static), every JS/TS server extension, the containerized-Node case, and the ambiguous-browser-script case;isStandardizableacross Node /unknown/ blank / legacy-custom / non-Node / Apple types plus a guard pinningappSchema's default type; client parity tests forNON_NODE_TYPESandNON_STANDARDIZABLE_TYPES; an explicit assertion thatNON_PM2_TYPESwas not widened; end-to-endstreamDetectionruns over temp repos for each new type plus the "package.json present ⇒ staysunknown", "served repo with an index.html stays standardizable", and "Apple type not overwritten" guards; a route test that a companionrepoPathcannot redirect the standardizer;standardizeRefusalForrefusals for the four new types; and wizard tests that the standardize card is absent for non-Node types, present forunknown, and that the banner explains why.Review
Self-review caught that
appSchemadefaultstypetoexpress. Headlessclaudereview returned no findings. Headlesscodexran three rounds and every finding is fixed in this branch: the closed allowlist that would have hidden the button for legacy persisted types,index.htmlbeside a server misclassified asstatic, server entries checked after docker and only matching.js,app.js/main.jswrongly counted as server evidence, and the/api/standardize/*target resolution preferring a companionrepoPathover the record it just type-checked.Closes #4137