diff --git a/.arcconfig b/.arcconfig
--- a/.arcconfig
+++ b/.arcconfig
@@ -1,4 +1,6 @@
 {
     "project_id" : "task-depcheck",
     "conduit_uri" : "https://phab.qadevel.cloud.fedoraproject.org",
+    "arc.land.onto.default" : "master",
+    "arc.feature.start.default" : "master"
 }
diff --git a/depcheck.yml b/depcheck.yml
--- a/depcheck.yml
+++ b/depcheck.yml
@@ -19,7 +19,7 @@
           arch: "{{ arch }}"
           rpms: "{{ workdir }}"
           repos: "{{ repos }}"
-          out_format: "tap"
+          report_format: "update"
       export: depcheck_output
 
     - name: report results to resultsdb
diff --git a/depcheck/__init__.py b/depcheck/__init__.py
--- a/depcheck/__init__.py
+++ b/depcheck/__init__.py
@@ -27,14 +27,8 @@
 
 # Check to see if libtaskotron is present; if so, import pass and fail
 
-try:
-    from libtaskotron import tap
-
-    Pass = tap.PASS
-    Fail = tap.FAIL
-except ImportError:
-    Pass = 0
-    Fail = 1
+Pass = 'PASSED'
+Fail = 'FAILED'
 
 import os.path
 import sys
@@ -63,63 +57,74 @@
     return namedrepo.split(':', 1)
 
 
-def depcheck_cli():
-    parser = argparse.ArgumentParser()
-    parser.add_argument("rpm", nargs='+', help="RPM to be tested")
-    parser.add_argument("-a", "--arch", choices=["i386", "x86_64", "armhfp"], help="""
-                        Architecture to be tested; support arches are x86, x86_64, and armhfp.
-                        If omitted, defaults to current machine architecture.
-                        """)
-    parser.add_argument("-r", "--repos", nargs='+', action='store', help="""
-                        Repo to be tested against, in the form of reponame:repolocation where
-                        repolocation can be local (in the form /dir/of/repo) or
-                        remote (direct link or standard metalink), to be tested against.
-                      """)
-    parser.add_argument("--release", help="Fedora release to be tested")
-    parser.add_argument("-f", "--format", choices=["json", "tap"], help="""
-                        Specify output format to override configuration
-                        """)
-
-    args = parser.parse_args()
+def run(rpms, arch, repos, report_format="builds"):
 
     rpmlist = []
     # handle directory args, expanding them to a list of rpm files with absolute
     # paths
-    for filename in args.rpm:
+    for filename in rpms:
         if os.path.isdir(filename):
             rpmlist.extend(dir_to_rpms(filename))
         else:
             rpmlist.append(filename)
 
-    # autodetect arch if none is passed in
-    if args.arch is None:
-        arch = platform.machine()
-    else:
-        arch = args.arch
-
     # arm has some special case requirements for arch handling
-    # Fixup for arm; the yum repo is labled as armhfp but the envr is armv7hl
+    # Fixup for arm; the yum repo is labled as armhfp but the nvr is armv7hl
     if arch == 'armv7l':
         arch = 'armhfp'
 
     # handle data formatting for repo metadata (list of dicts)
     repos = {}
-    for repo in args.repos:
+    for repo in repos:
         namedrepo = extract_named_repo(repo)
-        repos[namedrepo[0]] = {'arch': args.arch, 'path': namedrepo[1]}
+        repos[namedrepo[0]] = {'arch': arch, 'path': namedrepo[1]}
 
     workdir = tempfile.mkdtemp('', 'depcheck-', '/var/tmp/')
     md_handle = metadata.DepcheckMetadata(repos, targetdir=workdir)
 
     repomd_files = md_handle.repodata
 
-    output = []
+    output = None
+
+    depck = Depcheck(arch, repomd_files, workdir=workdir)
+    run_result = depck.check_rpm_deps(rpmlist)
+
+    if report_format == 'updates':
+        results = squash_results.squash_build_results_to_updates(run_result)
+        output = squash_results.check.export_TAP(results)
+    else:
+        output = json.dumps(run_result, indent=4, separators=(',', ': '))
+
+    return output
+
+def depcheck_cli():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("rpm", nargs='+', help="RPM to be tested")
+    parser.add_argument("-a", "--arch", choices=["i386", "x86_64", "armhfp"], help="""
+                        Architecture to be tested; support arches are x86, x86_64, and armhfp.
+                        If omitted, defaults to current machine architecture.
+                        """)
+    parser.add_argument("-r", "--repos", nargs='+', action='store', help="""
+                        Repo to be tested against, in the form of reponame:repolocation where
+                        repolocation can be local (in the form /dir/of/repo) or
+                        remote (direct link or standard metalink), to be tested against.
+                      """)
+    parser.add_argument("-f", "--format", choices=["builds", "updates"], help="""
+                        Specify output format to override configuration
+                        """)
+
+
+    args = parser.parse_args()
+    # autodetect arch if none is passed in
+    arch = args.arch
+    if arch is None:
+        arch = platform.machine()
+
+
     try:
-        depck = depcheck.Depcheck(arch, repomd_files, workdir=workdir)
-        depck.check_rpm_deps(rpmlist)
-        output = depck.get_output(output_format=args.format)
+        print run(args.rpm, arch, args.repos, args.format)
     except KeyboardInterrupt, e:
         print >> sys.stderr, ("\n\nExiting on user cancel.")
         sys.exit(1)
 
-    print output
+
diff --git a/depcheck/depcheck.py b/depcheck/depcheck.py
--- a/depcheck/depcheck.py
+++ b/depcheck/depcheck.py
@@ -30,16 +30,16 @@
 import os
 import tempfile
 
-from .output import format_out
 from . import Pass, Fail
 
+from libtaskotron.rpm_utils import rpmformat
+
 config_file = "/etc/depcheck.conf"
 
 # TODO: handle configuration file
 
-# FIXME: this will eventually be a util in libtaskotron
-def rpm_filename_to_envr(filename):
-    return os.path.basename(filename)[:-4]
+class DepcheckException(Exception):
+    pass
 
 
 class Depcheck(object):
@@ -62,8 +62,6 @@
         self.repos = repodata
         self.solvpool_object = None
         self.solvrepo_object = None
-        self.run_result = []
-
         if workdir is None:
             self.workdir = tempfile.mkdtemp('', 'depcheck-', '/var/tmp/')
         else:
@@ -132,37 +130,44 @@
 
         :param rpmfiles: local absolute paths to rpms to check
         :type rpmfiles: list of str
+        :returns: dictionary of results {NVR: {'results': Pass/Fail, 'details': []}}
         """
 
         self._prepare_libsolv()
 
+        run_result = {}
+
         for rpmfile in rpmfiles:
             print 'running depcheck for %s' % rpmfile
             result_state, result_details = self._check_rpm(rpmfile)
 
-            self.run_result.append({'envr': rpm_filename_to_envr(rpmfile),
-                                    'result': result_state,
-                                    'results': result_details})
+            nvr = self.rpm_filename_to_nvr(rpmfile)
+            if nvr in run_result:
+                if result_state != run_result[nvr]['result']:
+                    #TODO maybe do something saner, ideas welcomed
+                    raise DepcheckException("NVR %r has multiple conflicting results" % nvr)
+                else:
+                    run_result[nvr]['details'].append(result_details)
 
-    def get_output(self, output_format='tap'):
-        """Get formatted run result output. Will return empty if do_depcheck has
-        not yet been run
+            else:
+                run_result[nvr] = {'result': result_state,
+                                   'details': result_details}
 
-        :param output_format: output format to use, can be one of 'tap' or 'json'
-        :type output_format: str
-        :returns: formatted run result
-        """
+        return run_result
 
-        return format_out(self.run_result, output_format)
 
+    # FIXME: this will eventually be a util in libtaskotron
+    def rpm_filename_to_nvr(self, filename):
+        """
+        Transforms rpm filename to NVR.
+        We assume the Fedora environment, where all the rpm filenames
+        are in the NVRA.rpm format.
+
+        :param filename: name of the rpm file
+        :type filename: str
+        :returns: NVR
+        """
+        name = os.path.basename(filename)[:-len('.rpm')]
+        return rpmformat(name, 'nvr')
 
-def run(rpms, arch, release, repos, out_format):
-    # for the moment, make the following assumptions:
-    # - arch is specified and correct
-    # - all repos are passed in the form of local paths
-    # - output format is tap
-    # - all rpms are passed in as a list - no dirs
 
-    # setup solv pool
-    # collate results
-    pass
diff --git a/depcheck/output.py b/depcheck/output.py
deleted file mode 100644
--- a/depcheck/output.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# Copyright 2014 John H. Dulaney <jdulaney@fedoraproject.org>
-#
-# Licensed under the GNU General Public License Version 2
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-#
-# Depcheck mk 2
-
-import json
-from . import Pass, Fail
-
-
-def json_out(results):
-    """Generate JSON output"""
-    mod_results = []
-    for result in results:
-        if result['result'] == Pass:
-            result['result'] = 'Pass'
-        else:
-            result['result'] = 'Fail'
-        mod_results = mod_results + [result]
-
-    jsongen = json.dumps(results, indent=4, separators=(',', ': '))
-    return jsongen
-
-
-def tap_out(results):
-    """Generate TAP output"""
-    try:
-        from libtaskotron import tap
-    except ImportError:
-        raise Exception("libtaskotron not present")
-
-    tapgen = tap.TAPGenerator()
-    tapout = []
-    for result in results:
-        tapout.append(tapgen.format_TAP_msg(result['result'], 'depcheck: %s' % result['envr'],
-                                            desc='depcheck  results for %s' % result['envr'],
-                                            data={'output': result['results']}))
-
-    return tapout
-
-
-def format_out(results, out_format):
-    """We have our output, now to format it"""
-
-    if out_format == "tap":
-        return tap_out(results)
-    elif out_format == "json":
-        return json_out(results)
diff --git a/depcheck/squash_results.py b/depcheck/squash_results.py
new file mode 100644
--- /dev/null
+++ b/depcheck/squash_results.py
@@ -0,0 +1,54 @@
+from libtaskotron.bodhi_utils import BodhiUtils
+from libtaskotron import check
+from . import Pass, Fail
+
+def squash_build_results_to_updates(run_result, bodhi = None):
+    """
+    Converts the per-build results created by check_rpm_deps into per-update
+    results.
+    :param run_result: output of the check_rpm_deps method
+    :type run_result: dict
+    :returns: list of CheckDetail
+    """
+    if bodhi is None:
+        bodhi = BodhiUtils()
+
+    pending_ok, pending_fail = bodhi.build2update(run_result.keys(), strict = True)
+
+    updates = {}
+    for _, update in pending_ok.items():
+        updates[update['title']] = update
+
+    for _, update in pending_fail.items():
+        if update['title'] not in updates:
+            updates[update['title']] = update
+
+    updates_results = []
+    for update_title, update in updates.items():
+        cd = check.CheckDetail(update_title, check.ReportType.BODHI_UPDATE, outcome = 'PASSED')
+        updates_results.append(cd)
+
+        for build in update['builds']:
+            # If build is in pending_fail either when it has no assigned
+            #  update (that can not happen, as we traverse builds of
+            #  a specific update), or when an update was incomplete
+            #  (e.g. missing some builds).
+            # If an update is incomplete, all it's builds are put
+            #  into the pending_fail, so this should fail early,
+            if build in pending_fail:
+                cd.update_outcome('ABORTED')
+                cd.store('Update was incomplete.')
+                break
+
+            if build not in pending_ok:
+                cd.update_outcome('ABORTED')
+                cd.store('Update contains build that was not tested.')
+                break
+
+            if run_result[build]['result'] is not Pass:
+                cd.update_outcome('FAILED')
+                cd.store('Build %s failed depcheck' % build)
+                cd.output.extend(run_result[build]['details'])
+
+    return updates_results
+
diff --git a/etc/depcheck.conf b/etc/depcheck.conf
--- a/etc/depcheck.conf
+++ b/etc/depcheck.conf
@@ -1,8 +1,8 @@
 # Configuration file for depcheck mk 2
 
 [configuration]
-# output format; so far tap and json are available
-format=tap
+# output format; build or update
+format=builds
 
 
 [released]
diff --git a/run_depcheck.py b/run_depcheck.py
--- a/run_depcheck.py
+++ b/run_depcheck.py
@@ -1,45 +1,9 @@
 #!/usr/bin/python
 
 import depcheck
-from depcheck.depcheck import Depcheck
-from depcheck import dir_to_rpms, extract_named_repo
-from depcheck.metadata import DepcheckMetadata
-import os
-import tempfile
-
-def run(rpms, arch, repos, out_format="tap"):
-    """Entry point for depcheck"""
-    rpmlist = []
-    # handle directory args, expanding them to a list of rpm files with absolute
-    # paths
-    for filename in rpms:
-        if os.path.isdir(filename):
-            rpmlist.extend(dir_to_rpms(filename))
-        else:
-            rpmlist.append(filename)
-
-    # arm has some special case requirements for arch handling
-    # Fixup for arm; the yum repo is labled as armhfp but the envr is armv7hl
-    if arch == 'armv7l':
-        arch = 'armhfp'
-
-    # handle data formatting for repo metadata (list of dicts)
-    repos = {}
-    for repo in repos:
-        namedrepo = extract_named_repo(repo)
-        repos[namedrepo[0]] = {'arch': args.arch, 'path': namedrepo[1]}
-
-    workdir = tempfile.mkdtemp('', 'depcheck-', '/var/tmp/')
-    md_handle = DepcheckMetadata(repos, targetdir=workdir)
-
-    repomd_files = md_handle.repodata
-
-    output = []
-    
-    depck = Depcheck(arch, repomd_files, workdir=workdir)
-    depck.check_rpm_deps(rpmlist)
-    output = depck.get_output(output_format=args.format)
 
+def run(rpms, arch, repos, report_format="builds"):
+    return depcheck.run(rpms, arch, repos, report_format)
 
 if __name__ == '__main__':
     depcheck.depcheck_cli()
diff --git a/tests/functest_depcheck_scenarios.py b/tests/functest_depcheck_scenarios.py
--- a/tests/functest_depcheck_scenarios.py
+++ b/tests/functest_depcheck_scenarios.py
@@ -16,8 +16,10 @@
 import depcheck
 from depcheck.depcheck import Depcheck
 from depcheck import metadata
+from depcheck import Pass
 from .depcheck_scenarios import yaml_to_repos
 
+import pprint
 
 def get_depcheck_scenarios():
     """assuming that the depcheck_scenarios repo is checked out in the tests/
@@ -45,24 +47,23 @@
     return request.param
 
 
-def is_tap_output_pass(tap_output):
-    for output in tap_output:
-        if output.startswith('not ok'):
-            return False
-    return True
-
+def is_output_pass(output):
+    result = True
+    for key, value in output.items():
+        result = result and (value['result'] == Pass)
+    return result
 
 def read_yamldata(filename):
     with open(filename, 'r') as filedata:
         return yaml.load(filedata)
 
 
-def test_depcheck_scenarios(depcheck_scenario, tmpdir):
+def test_depcheck_scenarios(depcheck_scenario, tmpdir, monkeypatch):
     # decode the input tuple for readability
     scenario_pass = depcheck_scenario[0]
     scenario_path = depcheck_scenario[1]
 
-    print "running scenario %s" % scenario_path
+    print "\n### Running scenario %s" % scenario_path
 
     # convert the yaml scenario to fake rpms and repos
     scenario_data = read_yamldata(scenario_path)
@@ -80,7 +81,8 @@
 
     # run depcheck on the scenario's repos and rpms
     depck = Depcheck('x86_64', repomd_files)
-    depck.check_rpm_deps(rpm_list)
-    output = depck.get_output()
+    run_result = depck.check_rpm_deps(rpm_list)
+
+    pprint.pprint(run_result)
 
-    assert is_tap_output_pass(output) is scenario_pass
+    assert is_output_pass(run_result) is scenario_pass
diff --git a/tests/test_depcheck.py b/tests/test_depcheck.py
--- a/tests/test_depcheck.py
+++ b/tests/test_depcheck.py
@@ -1,10 +1,12 @@
+import pytest
 from dingus import Dingus
 import __builtin__
 
 from depcheck import depcheck
+from depcheck import Pass, Fail
 
 
-class TestDepcheckLibsolvPrep(object):
+class TestDepcheck(object):
     def test_libsolv_setup_adds_repos(self, monkeypatch):
         ref_arch = 'x86_64'
         ref_basepath = '/path/to/testing-repo/'
@@ -24,3 +26,56 @@
 
         assert len(stub_solvrepo.calls()) == 2
 
+    def test_check_rpm_deps_output(self, monkeypatch):
+        """
+        Tests whether rpm_deps provides correct output for a given input.
+        """
+        return_input = lambda x: x
+
+        test_depcheck = depcheck.Depcheck(None, None, "fakedir")
+        monkeypatch.setattr(test_depcheck, '_prepare_libsolv', Dingus())
+        monkeypatch.setattr(test_depcheck, 'rpm_filename_to_nvr', return_input)
+
+        rpmfiles = ['rpm_1', 'rpm_2']
+        def fake_check_rpm(rpmfile):
+            results = {
+                    rpmfiles[0]: (Pass, []),
+                    rpmfiles[1]: (Fail, ['message']),
+                    }
+            return results.get(rpmfile, (Pass, []))
+
+        monkeypatch.setattr(test_depcheck, '_check_rpm', fake_check_rpm)
+
+        outcome = test_depcheck.check_rpm_deps(rpmfiles)
+
+        assert len(outcome) == 2
+        assert 'rpm_1' in outcome
+        assert 'rpm_2' in outcome
+        assert outcome['rpm_1']['result'] == Pass
+        assert outcome['rpm_2']['result'] == Fail
+        assert outcome['rpm_2']['details'] == ['message']
+
+    def test_check_rpm_deps_conflicting_results(self, monkeypatch):
+        """
+        Checks whether rpm_deps raises Exceptions when conflicting
+        results are provided for one NVR.
+        """
+        return_input = lambda x: x
+
+        test_depcheck = depcheck.Depcheck(None, None, "fakedir")
+        monkeypatch.setattr(test_depcheck, '_prepare_libsolv', Dingus())
+        monkeypatch.setattr(test_depcheck, 'rpm_filename_to_nvr', return_input)
+
+        self.tmp_result = Pass
+        def fake_check_rpm(rpmfile):
+            if self.tmp_result == Pass:
+                self.tmp_result = Fail
+            else:
+                self.tmp_result = Pass
+            return (self.tmp_result, [])
+
+        monkeypatch.setattr(test_depcheck, '_check_rpm', fake_check_rpm)
+
+        with pytest.raises(depcheck.DepcheckException):
+            outcome = test_depcheck.check_rpm_deps(['rpm1', 'rpm1'])
+
diff --git a/tests/test_squash_results.py b/tests/test_squash_results.py
new file mode 100644
--- /dev/null
+++ b/tests/test_squash_results.py
@@ -0,0 +1,113 @@
+import pytest
+from dingus import Dingus
+import __builtin__
+
+from depcheck.squash_results import squash_build_results_to_updates
+from depcheck import Pass, Fail
+
+
+class TestSquashResults(object):
+    def test_squash_build_results_to_updates_all_ok(self):
+        """
+        Checks whether _squash_build_results_to_updates provides correct output
+        when all the builds in an update passed depchecking.
+        """
+        run_results = {
+                'nvr1': {'result': Pass, 'details': []},
+                'nvr2': {'result': Pass, 'details': []},
+                }
+        update = {'title': 'update_name', 'builds': run_results.keys()}
+        build_2_update = (
+                {
+                    'nvr1': update,
+                    'nvr2': update,
+                }, {})
+
+        fake_bodhi = Dingus(build2update__returns = build_2_update)
+
+        outcome = squash_build_results_to_updates(run_results, fake_bodhi)
+        assert len(outcome) == 1
+
+        detail = outcome[0]
+        assert detail.item == update['title']
+        assert detail.outcome == 'PASSED'
+
+    def test_squash_build_results_to_updates_build_failed(self):
+        """
+        Checks whether _squash_build_results_to_updates provides correct output
+        when some of the builds in an update failed depchecking.
+        """
+        run_results = {
+                'nvr1': {'result': Pass, 'details': []},
+                'nvr2': {'result': Fail, 'details': ['### TEST @@@']},
+                }
+        update = {'title': 'update_name', 'builds': run_results.keys()}
+        build_2_update = (
+                {
+                    'nvr1': update,
+                    'nvr2': update,
+                }, {})
+
+        fake_bodhi = Dingus(build2update__returns = build_2_update)
+
+        outcome = squash_build_results_to_updates(run_results, fake_bodhi)
+        assert len(outcome) == 1
+
+        detail = outcome[0]
+        assert detail.item == update['title']
+        assert detail.outcome == 'FAILED'
+        assert '### TEST @@@' in detail.output
+
+    def test_squash_build_results_to_updates_incomplete_update(self):
+        """
+        Checks whether _squash_build_results_to_updates provides correct output
+        when some update is incomplete (i.e. present in the second return-value
+        from the build2update method).
+        """
+        run_results = {
+                'nvr1': {'result': Pass, 'details': []},
+                'nvr2': {'result': Pass, 'details': []},
+                }
+        update = {'title': 'update_name', 'builds': run_results.keys()}
+        build_2_update = ({},
+                {
+                    'nvr1': update,
+                    'nvr2': update,
+                })
+
+        fake_bodhi = Dingus(build2update__returns = build_2_update)
+
+        outcome = squash_build_results_to_updates(run_results, fake_bodhi)
+        assert len(outcome) == 1
+
+        detail = outcome[0]
+        assert detail.item == update['title']
+        assert detail.outcome == 'ABORTED'
+        assert 'Update was incomplete.' in detail.output
+
+    def test_squash_build_results_to_updates_missing_build(self):
+        """
+        Checks whether _squash_build_results_to_updates provides correct output
+        when update is missing a result for one of its builds.
+        """
+        run_results = {
+                'nvr1': {'result': Pass, 'details': []},
+                'nvr2': {'result': Pass, 'details': []},
+                }
+        update = {'title': 'update_name', 'builds': run_results.keys()}
+        update['builds'].append('nvr3')
+        build_2_update = (
+                {
+                    'nvr1': update,
+                    'nvr2': update,
+                }, {})
+
+        fake_bodhi = Dingus(build2update__returns = build_2_update)
+
+        outcome = squash_build_results_to_updates(run_results, fake_bodhi)
+        assert len(outcome) == 1
+
+        detail = outcome[0]
+        assert detail.item == update['title']
+        assert detail.outcome == 'ABORTED'
+        assert 'Update contains build that was not tested.' in detail.output

