Command to generate release notes
diff --git a/pom.xml b/pom.xml
index ad30165..33a926e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -246,7 +246,7 @@
     <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
-      <version>4.3.5</version>
+      <version>4.5.7</version>
       <exclusions>
         <exclusion>
           <artifactId>commons-logging</artifactId>
@@ -294,6 +294,17 @@
       <artifactId>tomitribe-util</artifactId>
       <version>1.3.18</version>
     </dependency>
+    <dependency>
+      <groupId>org.tomitribe.jamira</groupId>
+      <artifactId>jamira-core</artifactId>
+      <version>0.4</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.13.2</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
 </project>
diff --git a/src/main/java/org/apache/openejb/tools/release/Loader.java b/src/main/java/org/apache/openejb/tools/release/Loader.java
index cc2c107..41a927e 100644
--- a/src/main/java/org/apache/openejb/tools/release/Loader.java
+++ b/src/main/java/org/apache/openejb/tools/release/Loader.java
@@ -18,6 +18,7 @@
 
 
 import org.apache.openejb.tools.release.cmd.Dist;
+import org.apache.openejb.tools.release.cmd.ReleaseNotes;
 
 import java.util.Arrays;
 import java.util.Iterator;
@@ -28,6 +29,7 @@
     public Iterator<Class<?>> iterator() {
         return Arrays.asList(
                 Dist.class,
+                ReleaseNotes.class,
                 Object.class
         ).iterator();
     }
diff --git a/src/main/java/org/apache/openejb/tools/release/cmd/ReleaseNotes.java b/src/main/java/org/apache/openejb/tools/release/cmd/ReleaseNotes.java
index 132f2ca..0867223 100644
--- a/src/main/java/org/apache/openejb/tools/release/cmd/ReleaseNotes.java
+++ b/src/main/java/org/apache/openejb/tools/release/cmd/ReleaseNotes.java
@@ -16,19 +16,131 @@
  */
 package org.apache.openejb.tools.release.cmd;
 
-import org.apache.openejb.tools.release.Command;
+import com.atlassian.jira.rest.client.api.SearchRestClient;
+import com.atlassian.jira.rest.client.api.domain.Issue;
+import com.atlassian.jira.rest.client.api.domain.IssueType;
+import com.atlassian.jira.rest.client.api.domain.SearchResult;
+import lombok.Getter;
 import org.apache.openejb.tools.release.Release;
+import org.tomitribe.crest.api.Command;
+import org.tomitribe.crest.api.Default;
+import org.tomitribe.crest.api.Option;
+import org.tomitribe.crest.api.PrintOutput;
+import org.tomitribe.jamira.core.Account;
+import org.tomitribe.jamira.core.Client;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * @version $Rev$ $Date$
  */
-@Command
+@Command("release-notes")
 public class ReleaseNotes {
 
+    /**
+     * Generate asciidoc release notes for the specified TomEE version.  The resulting
+     * asciidoc can be piped to a file in the `tomee-site-generator` repo.  For example:
+     *
+     *     release release-notes generate 8.0.7 > tomee-site-generator/src/main/jbake/content/tomee-8.0.7-release-notes.html
+     *
+     * In situations like TomEE 9, we can include release notes for the related TomEE 8
+     * version via the --include-versions flag as follows:
+     *
+     *     release release-notes generate 9.0.0-M7 --include-versions=8.0.7
+     *
+     * This tool leverages the Jamira command-line tool and library for logging into
+     * and talking with JIRA.  It is expected Jamira has been installed and the setup
+     * commands run so there is an account available for accessing JIRA.  See the
+     * description of --account for more details.
+     *
+     * @param version The TomEE version as specified in JIRA.  Example "8.0.7"
+     * @param includeVersions Any additional versions that should also be represented in these release notes.
+     * @param account The account to use to log into JIRA.  See https://github.com/tomitribe/jamira#setup
+     */
+    @Command
+    public PrintOutput generate(final String version,
+                                @Option("include-versions") final String includeVersions,
+                                @Option("account") @Default("default") final Account account) throws ExecutionException, InterruptedException {
+
+        final Set<String> versions = new HashSet<>();
+        versions.add(version);
+        if (includeVersions != null) {
+            versions.addAll(Arrays.asList(includeVersions.split(" *, *| ")));
+        }
+
+        final Client client = account.getClient();
+        final SearchRestClient searchClient = client.getSearchClient();
+
+        final Map<String, Issue> issuesByKey = new HashMap<>();
+        for (final String ver : versions) {
+            final String s = "project = TOMEE AND status = Resolved AND fixVersion = " + ver;
+            final SearchResult result = searchClient.searchJql(s).get();
+
+            for (final Issue issue : result.getIssues()) {
+                issuesByKey.put(issue.getKey(), issue);
+            }
+        }
+
+        final List<IssueType> sections = Arrays.asList(
+                client.getIssueType("Dependency upgrade"),
+                client.getIssueType("New Feature"),
+                client.getIssueType("Improvement"),
+                client.getIssueType("Task"),
+                client.getIssueType("Sub-task")
+        );
+
+        return out -> {
+            out.println("= Apache TomEE " + version + " Release Notes\n" +
+                    ":index-group: Release Notes\n" +
+                    ":jbake-type: page\n" +
+                    ":jbake-status: published");
+
+            for (final IssueType section : sections) {
+
+                final List<Issue> issues = issuesByKey.values()
+                        .stream().filter(issue -> issue.getIssueType().getName().equals(section.getName()))
+                        .collect(Collectors.toList());
+
+                if (issues.size() <= 0) continue;
+
+                out.println();
+                out.printf("== %s%n", section.getName());
+                out.println();
+
+                if (section.getName().equals("Dependency upgrade")) {
+                    issues.stream()
+                            .map(Upgrade::new)
+                            .sorted(Comparator.comparing(Upgrade::getSummary))
+                            .forEach(upgrade -> {
+                                out.printf(" - link:https://issues.apache.org/jira/browse/%s[%s] %s%n",
+                                        upgrade.getKey(),
+                                        upgrade.getKey(),
+                                        upgrade.getSummary());
+
+                            });
+                } else {
+                    for (final Issue issue : issues) {
+                        out.printf(" - link:https://issues.apache.org/jira/browse/%s[%s] %s%n",
+                                issue.getKey(),
+                                issue.getKey(),
+                                issue.getSummary());
+                    }
+                }
+            }
+        };
+    }
+
     public static void main(final String[] args) throws Throwable {
         final List<String> argsList = new ArrayList<String>();
 
@@ -46,4 +158,56 @@
 
         org.codehaus.swizzle.jirareport.Main.main((String[]) argsList.toArray(new String[]{}));
     }
+
+    @Getter
+    public static class Upgrade {
+        private final Issue issue;
+        private final String summary;
+
+        public Upgrade(final Issue issue) {
+            this.issue = issue;
+            this.summary = normalize(issue.getSummary());
+        }
+
+        public String getKey() {
+            return issue.getKey();
+        }
+
+        public static String normalize(final String summary) {
+            return Replace.string(summary)
+                    .first("^(upgrade to|upgrade|update to|update) (.*)", "$2")
+                    .all(" to ", " ")
+                    .all(" in tomee.*", "")
+                    .first("apache ", "")
+                    .toString();
+        }
+
+
+        private static class Replace {
+            private String string;
+
+            public Replace(final String string) {
+                this.string = string;
+            }
+
+            public Replace all(final String regex, final String replacement) {
+                string = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(string).replaceAll(replacement);
+                return this;
+            }
+
+            public Replace first(final String regex, final String replacement) {
+                string = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(string).replaceFirst(replacement);
+                return this;
+            }
+
+            public static Replace string(final String s) {
+                return new Replace(s);
+            }
+
+            @Override
+            public String toString() {
+                return string;
+            }
+        }
+    }
 }
diff --git a/src/test/java/org/apache/openejb/tools/release/cmd/ReleaseNotesTest.java b/src/test/java/org/apache/openejb/tools/release/cmd/ReleaseNotesTest.java
new file mode 100644
index 0000000..2e72ac1
--- /dev/null
+++ b/src/test/java/org/apache/openejb/tools/release/cmd/ReleaseNotesTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.openejb.tools.release.cmd;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ReleaseNotesTest {
+
+    @Test
+    public void normalizeDependencySummary() {
+        assertNormalize("BatchEE 0.6", "Upgrade BatchEE to 0.6");
+        assertNormalize("CXF 3.3.10 / 3.4.3", "Upgrade CXF to 3.3.10 / 3.4.3 in TomEE");
+        assertNormalize("CXF 3.3.8", "Update CXF 3.3.8");
+        assertNormalize("CXF 3.4.x (Java 16 support)", "Upgrade CXF 3.4.x (Java 16 support)");
+        assertNormalize("EclipseLink 2.7.7", "Update EclipseLink to 2.7.7");
+        assertNormalize("Implement JAX-RS SSE and add example", "Implement JAX-RS SSE and add example");
+        assertNormalize("Johnzon 1.2.9", "Apache Johnzon 1.2.9");
+        assertNormalize("Johnzon 1.2.9", "Update Johnzon 1.2.9");
+        assertNormalize("MyFaces 2.3.8", "Upgrade MyFaces 2.3.8");
+        assertNormalize("MyFaces 2.3.9", "Upgrade MyFaces to 2.3.9");
+        assertNormalize("OWB 2.0.22", "Update OWB 2.0.22");
+        assertNormalize("OpenSAML V3.4.6", "Update OpenSAML to V3.4.6");
+        assertNormalize("Tomcat 9.0.41", "Upgrade Tomcat 9.0.41");
+        assertNormalize("Tomcat 9.0.43", "Update Tomcat to 9.0.43");
+        assertNormalize("Tomcat 9.0.44", "Upgrade Tomcat to 9.0.44");
+        assertNormalize("Tomcat 9.0.45", "Update Tomcat to 9.0.45");
+        assertNormalize("bcprov-jdk15on 1.67", "Update bcprov-jdk15on to 1.67");
+        assertNormalize("quartz-openejb-shade", "Upgrade quartz-openejb-shade in TomEE 8/9");
+        assertNormalize("xbean 4.18+ (Java 16 support)", "Upgrade xbean to 4.18+ (Java 16 support)");
+    }
+
+    public static void assertNormalize(final String expected, final String input) {
+        assertEquals(expected, ReleaseNotes.Upgrade.normalize(input));
+    }
+
+}