diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5c4c8a7..fd5f7fe0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- #1178: Add `-password-env` and `-token-env` modifiers to the `repo` command to read the password/token from a named environment variable (secure alternatives to `-password` and `-token`).
+- #1081: Implement locate-resource command for resource-to-module mapping
## [0.10.8] - 2026-07-08
diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls
index 98d1fd40..3f1ee058 100644
--- a/src/cls/IPM/Main.cls
+++ b/src/cls/IPM/Main.cls
@@ -810,6 +810,13 @@ generate /my/path -export 00000,PacketName2,IgnorePacket2^00000,PacketName3,Igno
config get HistoryRetain
+
+ Find the module that owns a specific resource.
+
+ locate-resource %IPM.Main.cls
+ locate-resource %IPM.Common.inc
+
+
}
@@ -1098,6 +1105,8 @@ ClassMethod ShellInternal(
do ..Information(.tCommandInfo)
} elseif (tCommandInfo = "history") {
do ..History(.tCommandInfo)
+ } elseif (tCommandInfo = "locate-resource") {
+ do ..LocateResource(.tCommandInfo)
}
} catch pException {
if (pException.Code = $$$ERCTRLC) {
@@ -4325,6 +4334,20 @@ ClassMethod Update(ByRef pCommandInfo)
}
}
+ClassMethod LocateResource(ByRef CommandInfo)
+{
+ set resource = $get(CommandInfo("parameters","resource"))
+ set moduleName = ##class(%IPM.ExtensionBase.Utils).GetHomeModuleName(resource)
+ if (moduleName="") {
+ write $$$FormattedLine($$$Red, "Resource '"_ resource_"' is not currently part of an installed module.")
+ quit
+ }
+ set showFields = ""
+ write !
+ do ..GetListModules(,moduleName, .list)
+ do ..DisplayModules(.list,,,, .tModifiers)
+}
+
ClassMethod GetPythonInstalledLibs(Output list)
{
set target = ##class(%File).NormalizeDirectory("python", $system.Util.ManagerDirectory())
diff --git a/tests/unit_tests/Test/PM/Unit/CLI.cls b/tests/unit_tests/Test/PM/Unit/CLI.cls
index 04f45ed3..cf137bfd 100644
--- a/tests/unit_tests/Test/PM/Unit/CLI.cls
+++ b/tests/unit_tests/Test/PM/Unit/CLI.cls
@@ -658,4 +658,49 @@ Method TestSortListVersionPrerelease()
do $$$AssertEquals($listget(sorted(6),1), "snap", "version desc: snap (1.0.0-SNAPSHOT)")
}
+Method TestLocateCommand()
+{
+ set ipmModule = ##class(%IPM.Storage.Module).NameOpen("zpm",,.sc)
+ do $$$AssertStatusOK(sc, "Opened zpm module to get version")
+ set ipmVersion = ipmModule.VersionString
+
+ do $$$LogMessage("Testing locate-resource with .inc resource")
+ do ##class(%IPM.Utils.Module).BeginCaptureOutput(.cookie)
+ set status = ##class(%IPM.Main).Shell("locate-resource %IPM.Common.inc")
+ do ##class(%IPM.Utils.Module).EndCaptureOutput(cookie, .output)
+ do $$$AssertStatusOK(status, "locate-resource %IPM.Common.inc executed successfully")
+ set content = ""
+ set i = ""
+ for {
+ set i = $order(output(i))
+ quit:i=""
+ set content = content _ output(i) _ $char(10)
+ }
+ do $$$AssertTrue(content [ "zpm", "output contains module name 'zpm'")
+ do $$$AssertTrue(content [ ipmVersion, "output contains IPM version "_ipmVersion)
+
+ do $$$LogMessage("Testing locate-resource with .cls resource")
+ do ##class(%IPM.Utils.Module).BeginCaptureOutput(.cookie)
+ set status = ##class(%IPM.Main).Shell("locate-resource %IPM.Main.cls")
+ do ##class(%IPM.Utils.Module).EndCaptureOutput(cookie, .output)
+ do $$$AssertStatusOK(status, "locate-resource %IPM.Main.cls executed successfully")
+ set content = ""
+ set i = ""
+ for {
+ set i = $order(output(i))
+ quit:i=""
+ set content = content _ output(i) _ $char(10)
+ }
+ do $$$AssertTrue(content [ "zpm", "output contains module name 'zpm'")
+ do $$$AssertTrue(content [ ipmVersion, "output contains IPM version "_ipmVersion)
+
+ do $$$LogMessage("Testing locate-resource with non-existing resource")
+ set status = ..RunCommand("locate-resource Test.Sample.inc")
+ do $$$AssertStatusOK(status, "Gracefully handled non-existing resource")
+
+ do $$$LogMessage("Testing locate-resource without required argument")
+ set status = ..RunCommand("locate-resource")
+ do $$$AssertStatusNotOK(status, "Handled missing argument")
+}
+
}