diff --git a/.gitattributes b/.gitattributes
index 7322570c..f4243f1a 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -3,3 +3,5 @@
*.ps1 text eol=crlf
*.bat text eol=crlf
*.md text eol=lf
+
+.github/workflows/*.lock.yml linguist-generated=true merge=ours
diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json
new file mode 100644
index 00000000..5bfb38ee
--- /dev/null
+++ b/.github/aw/actions-lock.json
@@ -0,0 +1,24 @@
+{
+ "entries": {
+ "actions/github-script@v8": {
+ "repo": "actions/github-script",
+ "version": "v8",
+ "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd"
+ },
+ "github/gh-aw-actions/setup@v0.62.3": {
+ "repo": "github/gh-aw-actions/setup",
+ "version": "v0.62.3",
+ "sha": "4880241327b52a412519b3d987076538cdf8d745"
+ },
+ "github/gh-aw/actions/setup@v0.56.2": {
+ "repo": "github/gh-aw/actions/setup",
+ "version": "v0.56.2",
+ "sha": "f1073c5498ee46fec1530555a7c953445417c69b"
+ },
+ "github/gh-aw/actions/setup@v0.58.0": {
+ "repo": "github/gh-aw/actions/setup",
+ "version": "v0.58.0",
+ "sha": "cb7966564184443e601bd6135d5fbb534300070e"
+ }
+ }
+}
diff --git a/.github/workflows/backlog-cleanup-yakshaver.lock.yml b/.github/workflows/backlog-cleanup-yakshaver.lock.yml
new file mode 100644
index 00000000..70f8459b
--- /dev/null
+++ b/.github/workflows/backlog-cleanup-yakshaver.lock.yml
@@ -0,0 +1,1030 @@
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw (v0.62.3). DO NOT EDIT.
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# Not all edits will cause changes to this file.
+#
+# For more information: https://github.github.com/gh-aw/introduction/overview/
+#
+# Scans the YakShaver repos (Teams, Desktop, 360) for open epics whose sub-issue
+# progress is below 100%, analyses their PBIs across states, and produces a single
+# "Dry-Run Plan" report issue in SSWConsulting/SSW.YakShaver.Desktop. The report
+# proposes smaller theme epics (tagged by product), flags open issues that look
+# like duplicates of other work (open or closed) across the whole backlog, and
+# suggests source epics that could themselves be closed or archived. No epics are
+# created, linked, or modified — execution is deferred until a human confirms the plan.
+#
+# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"eeb0895223290cf0e244797f43fe84a62ef9eaeaac33c8b8d142d10a4c142294","compiler_version":"v0.62.3","strict":true,"agent_id":"copilot"}
+
+name: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+"on":
+ workflow_dispatch:
+ inputs:
+ source_repos:
+ default: SSWConsulting/SSW.YakShaver,SSWConsulting/SSW.YakShaver.Desktop,SSWConsulting/SSW.YakShaver360
+ description: Comma-separated list of owner/repo to scan for open epics
+ required: false
+ target_repo:
+ default: SSWConsulting/SSW.YakShaver.Desktop
+ description: Repo where the Dry-Run Plan report issue is created
+ required: false
+
+permissions: {}
+
+concurrency:
+ group: "gh-aw-${{ github.workflow }}"
+
+run-name: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+
+jobs:
+ activation:
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@4880241327b52a412519b3d987076538cdf8d745 # v0.62.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Generate agentic run info
+ id: generate_aw_info
+ env:
+ GH_AW_INFO_ENGINE_ID: "copilot"
+ GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI"
+ GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_INFO_VERSION: ""
+ GH_AW_INFO_AGENT_VERSION: "latest"
+ GH_AW_INFO_CLI_VERSION: "v0.62.3"
+ GH_AW_INFO_WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ GH_AW_INFO_EXPERIMENTAL: "false"
+ GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
+ GH_AW_INFO_STAGED: "true"
+ GH_AW_INFO_ALLOWED_DOMAINS: '[]'
+ GH_AW_INFO_FIREWALL_ENABLED: "true"
+ GH_AW_INFO_AWF_VERSION: "v0.24.5"
+ GH_AW_INFO_AWMG_VERSION: ""
+ GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_COMPILED_STRICT: "true"
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs');
+ await main(core, context);
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .github
+ .agents
+ sparse-checkout-cone-mode: true
+ fetch-depth: 1
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "backlog-cleanup-yakshaver.lock.yml"
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_INPUTS_SOURCE_REPOS: ${{ inputs.source_repos }}
+ run: |
+ bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
+ {
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_EOF'
+
+ Tools: create_issue, missing_tool, missing_data, noop
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
+ cat << 'GH_AW_PROMPT_EOF'
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF'
+ {{#runtime-import .github/workflows/backlog-cleanup-yakshaver.md}}
+ GH_AW_PROMPT_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_INPUTS_SOURCE_REPOS: ${{ inputs.source_repos }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_INPUTS_SOURCE_REPOS: ${{ inputs.source_repos }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
+ GH_AW_INPUTS_SOURCE_REPOS: process.env.GH_AW_INPUTS_SOURCE_REPOS
+ }
+ });
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh
+ - name: Upload activation artifact
+ if: success()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: activation
+ path: |
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ retention-days: 1
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ issues: read
+ pull-requests: read
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_WORKFLOW_ID_SANITIZED: backlogcleanupyakshaver
+ outputs:
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }}
+ model: ${{ needs.activation.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@4880241327b52a412519b3d987076538cdf8d745 # v0.62.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Set runtime paths
+ run: |
+ echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_ENV"
+ echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_ENV"
+ echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_ENV"
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - name: Configure gh CLI for GitHub Enterprise
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh
+ env:
+ GH_TOKEN: ${{ github.token }}
+ - env:
+ GH_AW_PAT: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ name: Validate GH_AW_CROSS_REPO_PAT
+ run: "if [ -z \"$GH_AW_PAT\" ]; then\n echo \"::error::GH_AW_CROSS_REPO_PAT secret is not configured.\"\n echo \"The workflow needs a fine-grained PAT with Issues: Read on all source repos and Issues: Read+Write on the target repo.\"\n exit 1\nfi\n"
+
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ (github.event.pull_request) || (github.event.issue.pull_request)
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ with:
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Install GitHub Copilot CLI
+ run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest
+ env:
+ GH_HOST: github.com
+ - name: Install AWF binary
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.24.5
+ - name: Determine automatic lockdown mode for GitHub MCP Server
+ id: determine-automatic-lockdown
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ with:
+ script: |
+ const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.24.5 ghcr.io/github/gh-aw-firewall/api-proxy:0.24.5 ghcr.io/github/gh-aw-firewall/squid:0.24.5 ghcr.io/github/gh-aw-mcpg:v0.1.19 node:lts-alpine
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
+ {"create_issue":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_EOF
+ - name: Write Safe Outputs Tools
+ run: |
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_EOF'
+ {
+ "description_suffixes": {
+ "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Issues will be created in repository \"${{ inputs.target_repo }}\"."
+ },
+ "repo_params": {},
+ "dynamic_tools": []
+ }
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
+ {
+ "create_issue": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "labels": {
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 128
+ },
+ "parent": {
+ "issueOrPRNumber": true
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ },
+ "temporary_id": {
+ "type": "string"
+ },
+ "title": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "missing_data": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "context": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "data_type": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "reason": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ }
+ }
+ GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
+ node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ # Mask immediately to prevent timing vulnerabilities
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${API_KEY}"
+
+ PORT=3001
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ DEBUG: '*'
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export DEBUG
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP Gateway
+ id: start-mcp-gateway
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }}
+ GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export MCP_GATEWAY_API_KEY
+ export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
+ mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ export DEBUG="*"
+
+ export GH_AW_ENGINE="copilot"
+ export GITHUB_PERSONAL_ACCESS_TOKEN="$GITHUB_MCP_SERVER_TOKEN"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_PERSONAL_ACCESS_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.19'
+
+ mkdir -p /home/runner/.copilot
+ cat << GH_AW_MCP_CONFIG_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "github": {
+ "type": "http",
+ "url": "https://api.githubcopilot.com/mcp/",
+ "headers": {
+ "Authorization": "Bearer \${GITHUB_PERSONAL_ACCESS_TOKEN}",
+ "X-MCP-Readonly": "true",
+ "X-MCP-Toolsets": "context,repos,issues,pull_requests"
+ },
+ "env": {
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_HOST": "\${GITHUB_SERVER_URL}"
+ },
+ "guard-policies": {
+ "allow-only": {
+ "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
+ "repos": "$GITHUB_MCP_GUARD_REPOS"
+ }
+ }
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ },
+ "guard-policies": {
+ "write-sink": {
+ "accept": [
+ "*"
+ ]
+ }
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
+ }
+ }
+ GH_AW_MCP_CONFIG_EOF
+ - name: Download activation artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: activation
+ path: /tmp/gh-aw
+ - name: Clean git credentials
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.24.5 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_PHASE: agent
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_STAGED: true
+ GH_AW_VERSION: v0.62.3
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_AUTHOR_NAME: github-actions[bot]
+ GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_COMMITTER_NAME: github-actions[bot]
+ XDG_CONFIG_HOME: /home/runner
+ - name: Detect inference access error
+ id: detect-inference-error
+ if: always()
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: |
+ # Copy Copilot session state files to logs folder for artifact collection
+ # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
+ SESSION_STATE_DIR="$HOME/.copilot/session-state"
+ LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
+
+ if [ -d "$SESSION_STATE_DIR" ]; then
+ echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
+ mkdir -p "$LOGS_DIR"
+ cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
+ echo "Session state files copied successfully"
+ else
+ echo "No session-state directory found at $SESSION_STATE_DIR"
+ fi
+ - name: Stop MCP Gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_CROSS_REPO_PAT,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN'
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_CROSS_REPO_PAT: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ - name: Append agent step summary
+ if: always()
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh
+ - name: Copy Safe Outputs
+ if: always()
+ run: |
+ mkdir -p /tmp/gh-aw
+ cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true
+ - name: Ingest agent output
+ id: collect_output
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs');
+ await main();
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP Gateway logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
+ if command -v awf &> /dev/null; then
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ else
+ echo 'AWF binary not installed, skipping firewall log summary'
+ fi
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: agent
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/agent-stdio.log
+ /tmp/gh-aw/agent/
+ /tmp/gh-aw/safeoutputs.jsonl
+ /tmp/gh-aw/agent_output.json
+ if-no-files-found: ignore
+ # --- Threat Detection (inline) ---
+ - name: Check if detection needed
+ id: detection_guard
+ if: always()
+ env:
+ OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }}
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ run: |
+ if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
+ echo "run_detection=true" >> "$GITHUB_OUTPUT"
+ echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
+ else
+ echo "run_detection=false" >> "$GITHUB_OUTPUT"
+ echo "Detection skipped: no agent outputs or patches to analyze"
+ fi
+ - name: Clear MCP configuration for detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
+ rm -f /home/runner/.copilot/mcp-config.json
+ rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
+ - name: Prepare threat detection files
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
+ for f in /tmp/gh-aw/aw-*.patch; do
+ [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ done
+ echo "Prepared threat detection files:"
+ ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ - name: Setup threat detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ WORKFLOW_DESCRIPTION: "Scans the YakShaver repos (Teams, Desktop, 360) for open epics whose sub-issue\nprogress is below 100%, analyses their PBIs across states, and produces a single\n\"Dry-Run Plan\" report issue in SSWConsulting/SSW.YakShaver.Desktop. The report\nproposes smaller theme epics (tagged by product), flags open issues that look\nlike duplicates of other work (open or closed) across the whole backlog, and\nsuggests source epics that could themselves be closed or archived. No epics are\ncreated, linked, or modified — execution is deferred until a human confirms the plan."
+ HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Execute GitHub Copilot CLI
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ id: detection_agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.24.5 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PHASE: detection
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_VERSION: v0.62.3
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_AUTHOR_NAME: github-actions[bot]
+ GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
+ GIT_COMMITTER_NAME: github-actions[bot]
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_detection_results
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
+ with:
+ name: detection
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+ - name: Set detection conclusion
+ id: detection_conclusion
+ if: always()
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }}
+ run: |
+ if [[ "$RUN_DETECTION" != "true" ]]; then
+ echo "conclusion=skipped" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection was not needed, marking as skipped"
+ elif [[ "$DETECTION_SUCCESS" == "true" ]]; then
+ echo "conclusion=success" >> "$GITHUB_OUTPUT"
+ echo "success=true" >> "$GITHUB_OUTPUT"
+ echo "Detection passed successfully"
+ else
+ echo "conclusion=failure" >> "$GITHUB_OUTPUT"
+ echo "success=false" >> "$GITHUB_OUTPUT"
+ echo "Detection found issues"
+ fi
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - safe_outputs
+ if: (always()) && ((needs.agent.result != 'skipped') || (needs.activation.outputs.lockdown_check_failed == 'true'))
+ runs-on: ubuntu-slim
+ permissions: {}
+ concurrency:
+ group: "gh-aw-conclusion-backlog-cleanup-yakshaver"
+ cancel-in-progress: false
+ outputs:
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@4880241327b52a412519b3d987076538cdf8d745 # v0.62.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: "1"
+ GH_AW_WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ with:
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs');
+ await main();
+ - name: Record Missing Tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ with:
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_WORKFLOW_ID: "backlog-cleanup-yakshaver"
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
+ GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
+ GH_AW_GROUP_REPORTS: "false"
+ GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
+ GH_AW_TIMEOUT_MINUTES: "20"
+ with:
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+ - name: Handle No-Op Message
+ id: handle_noop_message
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
+ GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+
+ safe_outputs:
+ needs: agent
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true')
+ runs-on: ubuntu-slim
+ permissions: {}
+ timeout-minutes: 15
+ env:
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/backlog-cleanup-yakshaver"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_SAFE_OUTPUTS_STAGED: "true"
+ GH_AW_WORKFLOW_ID: "backlog-cleanup-yakshaver"
+ GH_AW_WORKFLOW_NAME: "Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog"
+ outputs:
+ code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
+ code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }}
+ created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@4880241327b52a412519b3d987076538cdf8d745 # v0.62.3
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_ENV"
+ - name: Configure GH_HOST for enterprise compatibility
+ shell: bash
+ run: |
+ # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
+ # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
+ GH_HOST="${GITHUB_SERVER_URL#https://}"
+ GH_HOST="${GH_HOST#http://}"
+ echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"max\":1,\"target-repo\":\"${{ inputs.target_repo }}\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}"
+ GH_AW_SAFE_OUTPUTS_STAGED: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+
diff --git a/.github/workflows/backlog-cleanup-yakshaver.md b/.github/workflows/backlog-cleanup-yakshaver.md
new file mode 100644
index 00000000..072675c6
--- /dev/null
+++ b/.github/workflows/backlog-cleanup-yakshaver.md
@@ -0,0 +1,257 @@
+---
+name: Backlog Cleanup — Dry-Run Analysis of YakShaver Backlog
+description: |
+ Scans the YakShaver repos (Teams, Desktop, 360) for open epics whose sub-issue
+ progress is below 100%, analyses their PBIs across states, and produces a single
+ "Dry-Run Plan" report issue in SSWConsulting/SSW.YakShaver.Desktop. The report
+ proposes smaller theme epics (tagged by product), flags open issues that look
+ like duplicates of other work (open or closed) across the whole backlog, and
+ suggests source epics that could themselves be closed or archived. No epics are
+ created, linked, or modified — execution is deferred until a human confirms the plan.
+
+on:
+ workflow_dispatch:
+ inputs:
+ source_repos:
+ description: "Comma-separated list of owner/repo to scan for open epics"
+ required: false
+ default: "SSWConsulting/SSW.YakShaver,SSWConsulting/SSW.YakShaver.Desktop,SSWConsulting/SSW.YakShaver360"
+ target_repo:
+ description: "Repo where the Dry-Run Plan report issue is created"
+ required: false
+ default: "SSWConsulting/SSW.YakShaver.Desktop"
+
+runs-on: ubuntu-latest
+
+permissions:
+ contents: read
+ issues: read
+ pull-requests: read
+
+tools:
+ github:
+ mode: remote
+ toolsets: [default]
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ bash: true
+
+network: {}
+
+steps:
+ - name: Validate GH_AW_CROSS_REPO_PAT
+ run: |
+ if [ -z "$GH_AW_PAT" ]; then
+ echo "::error::GH_AW_CROSS_REPO_PAT secret is not configured."
+ echo "The workflow needs a fine-grained PAT with Issues: Read on all source repos and Issues: Read+Write on the target repo."
+ exit 1
+ fi
+ env:
+ GH_AW_PAT: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+
+safe-outputs:
+ staged: true
+ github-token: ${{ secrets.GH_AW_CROSS_REPO_PAT }}
+ create-issue:
+ max: 1
+ title-prefix: ""
+ target-repo: ${{ inputs.target_repo }}
+---
+
+# Backlog Cleanup — Dry-Run Analysis
+
+You are an expert product backlog manager. Your job is to **analyse** — not execute — a cleanup of the YakShaver product backlog across multiple repos and produce a single report issue that a human can review, tick off, and then separately execute.
+
+## Inputs
+
+- `source_repos` — comma-separated list of `owner/repo` to scan. Default: `SSWConsulting/SSW.YakShaver`, `SSWConsulting/SSW.YakShaver.Desktop`, `SSWConsulting/SSW.YakShaver360`.
+- `target_repo` — where the single Dry-Run Plan report issue is created. Default: `SSWConsulting/SSW.YakShaver.Desktop`.
+
+Parse `${{ inputs.source_repos }}` on commas and trim whitespace.
+
+## Product mapping
+
+Each source repo maps to a **product tag** used when labelling proposed new epics in the report:
+
+| Repo | Product tag |
+|------|-------------|
+| `SSWConsulting/SSW.YakShaver` | `teams` (also hosts shared backend / portal — tag as `shared` when a PBI is clearly backend/portal work rather than Teams UX) |
+| `SSWConsulting/SSW.YakShaver.Desktop` | `desktop` |
+| `SSWConsulting/SSW.YakShaver360` | `360` |
+
+When a cluster spans multiple products, pick the dominant one as the primary tag and list the others in the epic body.
+
+---
+
+## Step 1 — Discover in-scope epics
+
+For each repo in `source_repos`:
+
+1. Query issues where **issue type = "Epic"** (GitHub's native issue types, not a label) AND **state = OPEN**.
+2. For each epic, read its **sub-issue progress** (`completed` / `total`).
+3. **Skip epics where `completed == total`** — those are already done. Record them in the "Skipped epics" section of the report.
+4. For the remaining qualifying epics, read:
+ - Epic title, number, repo, labels, and body (first few paragraphs is enough)
+ - **All sub-issues (both open and closed)** — for each, read title, number, state, labels, last-updated date, **and the full issue body**. The full body is required so you have enough context to cluster accurately in Step 2 and to judge duplicate similarity in Step 3.
+
+**Closed sub-issues are in scope for clustering.** Treat them the same as open ones in Step 2 — a closed PBI still belongs in whichever new smaller epic matches its theme, so the future epic's progress bar reflects historical work. Do not drop closed sub-issues just because they are done.
+
+---
+
+## Step 2 — Cluster PBIs into proposed smaller epics
+
+Treat the PBIs from all in-scope epics as one unified pool and cluster them by theme.
+
+**Clustering rules:**
+- Each cluster should represent a coherent functional area (e.g. `🔌 MCP & AI Pipeline`, `🎬 Video Recording`, `♻️ Auth Migration`, `🐛 Desktop Bug Fixes`, `🏗️ Platform & Infrastructure`, `🎨 UX Improvements`).
+- **Cross-repo / cross-product clustering is encouraged** — a Teams PBI and a Desktop PBI on the same topic go in the same cluster.
+- Target **10–20 PBIs per cluster** when the pool allows; let the actual themes drive the count.
+- The total number of proposed new epics is **flexible** — do not force a fixed count.
+- A PBI spanning two themes goes in whichever is dominant.
+- All-closed clusters are acceptable — they show historical progress the future epic will inherit.
+
+Assign each cluster a product tag based on the dominant source:
+- `teams` — mostly PBIs from `SSW.YakShaver` that are clearly Teams UX
+- `desktop` — mostly PBIs from `SSW.YakShaver.Desktop`
+- `360` — mostly PBIs from `SSW.YakShaver360`
+- `shared` — backend / portal / infrastructure work that serves multiple products
+
+---
+
+## Step 3 — Detect duplicate candidates (whole backlog)
+
+Duplicate detection is **not** limited to PBIs under the in-scope epics. Scan the full backlog of each repo in `source_repos`.
+
+**Build two pools:**
+- **Left pool (candidates to flag):** every **open** issue in every `source_repos` repo — including issues that have no parent epic at all.
+- **Right pool (match targets):** every issue (open **or** closed) in every `source_repos` repo.
+
+For each open issue in the left pool, check whether it appears to duplicate an issue in the right pool:
+
+- Compare titles and first paragraphs of bodies for semantic similarity.
+- Look for explicit wording like "duplicate of", "supersedes", "same as" in either issue's body or comments.
+- Flag only where the signal is strong — precision matters more than recall here, because each flag costs a human triage step.
+
+**Do not flag** closed-vs-closed duplicates — they are historical noise and don't need action.
+
+**Do flag:**
+- Open ↔ closed — the open one may already be done, close it.
+- Open ↔ open — the two open issues overlap, merge or close one.
+
+For each flagged pair, record:
+- The open issue reference and title (always on the left)
+- The matched issue reference, its state (open / closed-on-date), and title
+- A one-line reason (e.g. "title near-match", "explicit 'duplicate of' in #X's comment", "body describes the same acceptance criteria")
+
+---
+
+## Step 4 — Suggest source epics to close or archive
+
+Review each in-scope source epic (the ones with progress < 100%) against your clustering. Suggest it for close/archive when:
+
+- All of its remaining open PBIs are duplicates flagged in Step 3.
+- All of its remaining open PBIs have been absorbed into proposed new smaller epics and the source epic adds no extra scope of its own.
+- Its body describes work that has been superseded by a decision or another initiative.
+
+Also list the 100%-done epics from Step 1 here — they are straightforward close candidates.
+
+---
+
+## Step 5 — Produce the Dry-Run Plan report issue
+
+Call `create_issue` **exactly once** with:
+
+- `title`: `🧹 Dry-Run Plan — YakShaver Backlog Cleanup (YYYY-MM-DD)` (substitute today's date)
+- `labels`: `Type: Refactor`, `cleanup`
+- `body`: use the template below
+- `temporary_id`: `aw_cleanup_report`
+
+Do not include `issue_type`, `repo`, or `target-repo` — the safe-output config handles the target repo.
+
+### Body template
+
+```markdown
+# 🧹 Dry-Run Plan — YakShaver Backlog Cleanup
+
+**Run date:** YYYY-MM-DD
+**Scanned repos:** SSWConsulting/SSW.YakShaver, SSWConsulting/SSW.YakShaver.Desktop, SSWConsulting/SSW.YakShaver360
+**In-scope epics:** N (open, sub-issue progress < 100%)
+**Total PBIs reviewed:** M (X open, Y closed)
+
+> This is an **analysis only** — no epics have been created, no sub-issues have been linked, and no existing issues have been modified. Tick the confirmation checklist at the bottom once you're satisfied with the plan, then trigger the execute workflow.
+
+---
+
+## 1. Proposed smaller epics
+
+### 🔌 Example Cluster Name — `product: desktop`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver.Desktop#677 — ✨ YakShaver Desktop App
+
+**Scope:** 2–3 sentences describing what this epic covers and the value it delivers.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver.Desktop#123 — [title] (open)
+- SSWConsulting/SSW.YakShaver.Desktop#124 — [title] (closed)
+- SSWConsulting/SSW.YakShaver#456 — [title] (open)
+
+[... one block per cluster ...]
+
+---
+
+## 2. Duplicate candidates (whole backlog)
+
+Open issues anywhere in the scanned repos (not limited to epic sub-issues) that look like they duplicate other work. Both open↔closed and open↔open pairs are included. Closed↔closed duplicates are omitted — they don't need action.
+
+| Open issue | Matched issue (state) | Reason |
+|------------|------------------------|--------|
+| SSWConsulting/SSW.YakShaver.Desktop#200 — [title] | SSWConsulting/SSW.YakShaver.Desktop#120 — [title] (closed 2025-11-02) | near-identical title and scope |
+| SSWConsulting/SSW.YakShaver#305 — [title] | SSWConsulting/SSW.YakShaver.Desktop#210 — [title] (open) | same acceptance criteria, cross-repo overlap |
+
+---
+
+## 3. Source epics suggested for close or archive
+
+Existing epics that can likely be closed once this plan is executed.
+
+| Epic | Reason |
+|------|--------|
+| SSWConsulting/SSW.YakShaver#2811 — ✨ YakShaver Agent 2.0 | All remaining open PBIs absorbed into proposed epics 1, 3, and 5 |
+
+---
+
+## 4. Epics skipped (already 100% complete)
+
+These were excluded from clustering because their sub-issue progress was 100%.
+
+- SSWConsulting/SSW.YakShaver#NNN — [title]
+
+---
+
+## 5. ✅ Confirmation checklist
+
+Tick each box once you've reviewed and agree. Add comments on this issue for anything you want changed before execution.
+
+- [ ] Proposed smaller epics (Section 1) look right
+- [ ] Duplicate candidates (Section 2) are genuine duplicates
+- [ ] Source epics in Section 3 should be closed/archived after execution
+- [ ] Ready to trigger the execute workflow
+
+> After all boxes are ticked, run the execute workflow (future) — it will read this issue and perform the actual epic creation, sub-issue linking, and original-epic updates.
+```
+
+Use plain `owner/repo#number` references everywhere (not Markdown links). GitHub auto-links them and this keeps the comment under GitHub's HTTP-link cap.
+
+---
+
+## Important notes
+
+- **No `create-issue` beyond the single report.** Do not attempt to create theme epics or a cleanup-report-per-product — one report issue only.
+- **No `link-sub-issue` calls.** Linking is deferred to the execute workflow.
+- **No `add-comment` calls.** Source-epic comments are not produced in this workflow.
+- The three scanned repos stay untouched — do not modify, close, or relabel any existing issue.
+- Include **all** PBIs from qualifying epics in the clusters — open and closed — so the proposed-epic scope reflects full context.
+- If a source repo is unreachable (e.g. PAT lacks access), note it in the report's Scope section and continue with what you can read.
+- The `GH_AW_CROSS_REPO_PAT` secret must have:
+ - **Read** access to every repo in `source_repos`
+ - **Read + Write (Issues)** access to `target_repo`
diff --git a/docs/audits/backlog-audit-2026-04-27-165630.md b/docs/audits/backlog-audit-2026-04-27-165630.md
new file mode 100644
index 00000000..122f3b33
--- /dev/null
+++ b/docs/audits/backlog-audit-2026-04-27-165630.md
@@ -0,0 +1,735 @@
+# 🧹 Dry-Run Audit — YakShaver Backlog
+
+**Run date:** 2026-04-27 16:56
+**Run ID:** 2026-04-27-165630
+**Scanned repos:** SSWConsulting/SSW.YakShaver, SSWConsulting/SSW.YakShaver.Desktop, SSWConsulting/SSW.YakShaver360
+**In-scope epics:** 20 (open, sub-issue progress < 100% — includes one empty-stub epic with 0 sub-issues)
+**Total PBIs reviewed:** 421 (120 open, 301 closed)
+**Duplicate candidates flagged:** 40
+**Source epics suggested for close:** 6 (all 100%-complete) + 2 thin/redundant candidates
+
+> Read-only audit. No GitHub issues were created, modified, closed, or commented on. This file is for human review.
+
+---
+
+## 0. Methodology & data summary
+
+### Queries used
+- **Epic discovery (paginated):** GraphQL `repository.issues(states:OPEN)` with `issueType{ name }`, filtered to nodes where `issueType.name == "Epic"`.
+- **Sub-issue progress per epic:** GraphQL `repository.issue(number).subIssuesSummary{ total completed percentCompleted }`.
+- **Sub-issue lists:** REST `repos///issues//sub_issues --paginate`, capturing `number, title, state, labels, body, updated_at`.
+- **Whole-backlog dedup pool:** `gh issue list -R --state all --limit --json number,title,state,updatedAt`.
+
+All calls were read-only (GraphQL `query { ... }` blocks; REST `GET`). No `-X POST/PATCH/PUT/DELETE`, no mutations, no `gh issue close/edit/create/comment/lock/transfer/reopen`, no `gh pr` mutating commands.
+
+### Repos accessed
+- ✅ `SSWConsulting/SSW.YakShaver` — 2569 issues total
+- ✅ `SSWConsulting/SSW.YakShaver.Desktop` — 476 issues total
+- ✅ `SSWConsulting/SSW.YakShaver360` — 44 issues total
+- No access failures.
+
+### Per-repo epic and sub-issue totals
+
+| Repo | Open Epics found | 100% Skipped | In-scope | Sub-issues fetched (open / closed) |
+|------|-----------------:|-------------:|---------:|-----------------------------------:|
+| `SSWConsulting/SSW.YakShaver` | 24 | 6 | 18 | 376 (111 / 265) |
+| `SSWConsulting/SSW.YakShaver.Desktop` | 2 | 0 | 2 | 40 (9 / 31) |
+| `SSWConsulting/SSW.YakShaver360` | 0 | 0 | 0 | — |
+| **Totals** | **26** | **6** | **20** | **421 (120 / 301)** |
+
+Notes:
+- One in-scope epic (`SSW.YakShaver#3583 — ✨ 🏍️ Performance Optimization`) has **0 sub-issues** but is still open, so it is included in the in-scope count and listed under "Source epics suggested for close" (Section 3).
+- Sub-issue numbers in the table above (e.g. 111/265) are the totals after de-duplicating sub-issues that appear under multiple parents — the project's clustering pool is **421**.
+
+### Whole-backlog dedup pool sizes
+
+| Repo | OPEN | CLOSED | Total |
+|------|-----:|-------:|------:|
+| `SSWConsulting/SSW.YakShaver` | 740 | 1829 | 2569 |
+| `SSWConsulting/SSW.YakShaver.Desktop` | 106 | 370 | 476 |
+| `SSWConsulting/SSW.YakShaver360` | 29 | 15 | 44 |
+| **Totals** | **875** | **2214** | **3089** |
+
+Dedup left pool = every OPEN issue (875). Dedup right pool = every issue, OPEN+CLOSED (3089). Closed↔closed matches were excluded by construction.
+
+### Detection method
+Title-similarity scan over the 875×3089 pair space using normalized titles (ASCII-only, lowercased, stop-words and ticket-prefix tokens removed) plus Jaccard token similarity ≥ 0.55 OR exact-normalized-title equality. Templated/series patterns ("Sprint TBD video to YouTube channel", per-person "Canary Testing - ") were filtered out as false positives. Final shortlist was hand-curated for genuine duplicates.
+
+---
+
+## 1. Proposed smaller epics
+
+> Each cluster lists the source epics it consolidates, a short scope statement, and the PBIs it should pull in (open + closed). All 421 in-scope sub-issues are accounted for across clusters 1–17.
+
+### 🤖 1. Agent 2.0 — Recording, Transcript, LLM & Prompt Pipeline — `product: desktop` (also `shared`)
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#1150 — 🚀 Agent - replace current workflow with full agentic capability
+- SSWConsulting/SSW.YakShaver#2811 — ✨ YakShaver Agent 2.0 (recording/transcription/prompt portions)
+- SSWConsulting/SSW.YakShaver#584 — small overlap (prompt name-recognition)
+
+**Scope:** The end-to-end YakShaver 2.0 engine: video recording → audio extraction → transcription → LLM call (prompt + structured output) → issue creation. Owns the Tauri/Electron recording subsystem, FFmpeg pipeline, OpenAI/Whisper integration, prompt design, model selection, and core agent architecture.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#1000 — ✨ Refactor Separation of Concerns Between Function App and Container App (open)
+- SSWConsulting/SSW.YakShaver#1001 — ✨ Refactor YakShaver Agent to Define Required Kernel Functions (Tools) (open)
+- SSWConsulting/SSW.YakShaver#1002 — ✨ Implement Database Design for Storing Chat History (open)
+- SSWConsulting/SSW.YakShaver#1003 — ✨ Add System Prompt Configuration for YakShaver Agent (open)
+- SSWConsulting/SSW.YakShaver#1004 — ✨ Define and Document the Boundary of a Vertical Slice (open)
+- SSWConsulting/SSW.YakShaver#1005 — ✨ Add Unit and Integration Tests for YakShaver Agent (open)
+- SSWConsulting/SSW.YakShaver#1016 — ✨ Implement Feature Flag for New Agent in YakShaver (open)
+- SSWConsulting/SSW.YakShaver#1334 — ✨ Doc - Draft High Level Approach How to Implement AI Agent (open)
+- SSWConsulting/SSW.YakShaver#587 — ♻️ OpenAI Prompt - Improve recognizing uncommon names (open)
+- SSWConsulting/SSW.YakShaver#76 — ✨ System Portal - Ability to switch AI models (open)
+- SSWConsulting/SSW.YakShaver#77 — ✨ System Portal - Ability to switch speech-to-text models (open)
+- SSWConsulting/SSW.YakShaver#80 — ✨ System Portal - Modify prompts (open)
+- SSWConsulting/SSW.YakShaver#2890 — ✨ MCP Issue Creation - Implement Fallback for YakShaver 2.0 (open)
+- SSWConsulting/SSW.YakShaver#2894 — ✨ Database Redesign (open)
+- SSWConsulting/SSW.YakShaver#2909 — ✨ Roadmap - Define Standalone App Requirements (open)
+- SSWConsulting/SSW.YakShaver#3012 — ✨ YakShaver 2.0 - Configurable Settings (open)
+- SSWConsulting/SSW.YakShaver#2786 — 🕵️ Spike: YakShaver Agent 2.0 (closed)
+- SSWConsulting/SSW.YakShaver#2813 — 🕵️ Spike: Investigate frameworks to build Standalone App (closed)
+- SSWConsulting/SSW.YakShaver#2814 — ✨ Investigate MCP Hosts/Clients (closed)
+- SSWConsulting/SSW.YakShaver#2815 — ✨ Event Storming 2.0 (closed)
+- SSWConsulting/SSW.YakShaver#2877 — ✨ Initialise YakShaver 2.0 Tauri App (closed)
+- SSWConsulting/SSW.YakShaver#2893 — ✨ Local Processing - Implement Local Video Processing (closed)
+- SSWConsulting/SSW.YakShaver#2895 — ✨ Screen Recording - Add screen capture and video review functionality (closed)
+- SSWConsulting/SSW.YakShaver#2953 — ✨ YakShaver 2.0 MVP - FFmpeg (closed)
+- SSWConsulting/SSW.YakShaver#2954 — ✨ YakShaver 2.0 MVP - Video Recording (closed)
+- SSWConsulting/SSW.YakShaver#2955 — ✨ YakShaver 2.0 MVP - Video Hosting (closed)
+- SSWConsulting/SSW.YakShaver#2956 — ✨ YakShaver 2.0 MVP - OpenAI - Process Transcript (closed)
+- SSWConsulting/SSW.YakShaver#2958 — ✨ YakShaver 2.0 MVP - OpenAI - Extract Transcript (closed)
+- SSWConsulting/SSW.YakShaver#2987 — ✨ YakShaver 2.0 MVP - Connect Recording to Transcription pipeline (closed)
+- SSWConsulting/SSW.YakShaver#2989 — ✨ YakShaver 2.0 MVP - Connect Recording to Upload Video (closed)
+- SSWConsulting/SSW.YakShaver#3009 — ✨ YakShaver 2.0 - Refine Prompt (closed)
+- SSWConsulting/SSW.YakShaver#3011 — ✨ Make LLM client generic for different models/services (closed)
+- SSWConsulting/SSW.YakShaver#175 — ✨ Generalize Transcription Model (closed)
+- SSWConsulting/SSW.YakShaver#287 — Add 3-2-1 countdown before recording starts (closed)
+- SSWConsulting/SSW.YakShaver#3005 — ✨ YakShaver 2.0: Add a control bar in recording (closed)
+- SSWConsulting/SSW.YakShaver#18 — ✨ Recording Toolbar - Minimize App Window During Recording (closed)
+- SSWConsulting/SSW.YakShaver#19 — ✨ Task Execution Details Enhancement (closed)
+- SSWConsulting/SSW.YakShaver#22 — ✨ YouTube Link Integration - Add YouTube URL to LLM Input (closed)
+- SSWConsulting/SSW.YakShaver#21 — ✨ YouTube Connection Update (closed)
+- SSWConsulting/SSW.YakShaver#118 — 🐛 Issue with YouTube Link in YakShaver Desktop (closed)
+- SSWConsulting/SSW.YakShaver#3385 — 🐛 Bug - Remote participant audio is not captured when recording system audio (closed)
+- SSWConsulting/SSW.YakShaver#354 — Add "No screen / Camera only" option (closed)
+- SSWConsulting/SSW.YakShaver#3158 — ✨ YakShaver Desktop - AI Assisted Development Workflow (closed)
+- SSWConsulting/SSW.YakShaver#436 — ♻️ Refactor custom prompt to properly embed screenshot (closed)
+- SSWConsulting/SSW.YakShaver#302 — ✨ Add screenshot upload tool to Yak video tools (closed)
+- SSWConsulting/SSW.YakShaver#425 — Save recorded video files and durations (closed)
+- SSWConsulting/SSW.YakShaver#443 — Workflow stops at 'Stop' but doesn't update YouTube metadata (closed)
+
+### 🎬 2. YakShaver Desktop App — Onboarding, OAuth, Distribution & Reliability — `product: desktop`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver.Desktop#677 — ✨ YakShaver Desktop App
+- SSWConsulting/SSW.YakShaver#2811 — ✨ YakShaver Agent 2.0 (distribution/onboarding/login portions)
+
+**Scope:** Everything around shipping the desktop app: code signing & notarization (Win/macOS), installers, auto-update, onboarding wizard, login flow, error reporting, in-app UX polish, settings, factory reset, telemetry, and bug fixes against the running desktop app.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver.Desktop#350 — Code signing & notarization - Windows (Authenticode) (open)
+- SSWConsulting/SSW.YakShaver.Desktop#3544 — Prompt access requires login but not communicated (open)
+- SSWConsulting/SSW.YakShaver.Desktop#471 — ✨ Stop Button - Add functionality to stop AI workflow (open)
+- SSWConsulting/SSW.YakShaver.Desktop#542 — ✨ Feature - Add additional LLM providers to the Desktop setup wizard (open)
+- SSWConsulting/SSW.YakShaver.Desktop#604 — 🐛 Bug - Failure to download YouTube video when using Process Link box (open)
+- SSWConsulting/SSW.YakShaver.Desktop#693 — ✨ Optimize Transcript - Fix spellings and optimize transcript (open)
+- SSWConsulting/SSW.YakShaver#3295 — 📝 Publish blogs about YakShaver Desktop (open)
+- SSWConsulting/SSW.YakShaver#3321 — 🧪 Canary Testing - Feedback from someone outside YakShaver team (open)
+- SSWConsulting/SSW.YakShaver.Desktop#3422 — 📝 Download page is confusing (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3439 — 🐛 Microsoft Auth shows "Unhandled protocol route: /auth" (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3440 — ✨ AI - Add Agents.md file (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3445 — ✨ Project Selection - Add project selection and metadata (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3451 — 🐛 Video processed successfully despite no audio (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3497 — ✨ Authentication-migration PBIs breakdown (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3498 — ✨ Jira Integration and Done Video (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3515 — 🐛 Update YakShaver.Desktop template (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#3543 — Errors are hard to notice in results log (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#526 — Add centralized error logging to App Insights (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#593 — ✨ Recording Confirmation - Prevent recording from starting until user confirms (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#642 — 🐛 Camera and audio not working despite permissions (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#646 — 🐛 Multiple 'Update ready' popups shown repeatedly (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#649 — 🕵️♂️ Spike - LLM API key not recognized (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#662 — 🐛 GitHub authentication fails when creating issue (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#665 — 🐛 Clicking taskbar/shortcut does not restore window on Windows (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#671 — 🐛 Undo shows as Completed in portal instead of Cancelled (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#718 — Add fallback/system prompt in desktop app when project prompt missing (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#734 — 🐛 PBI link can't be extracted (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#737 — ♻️ Refactor - Split onboarding wizard into per-tab components (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#746 — Provide Intel (x86_64) macOS installer (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#749 — 🐛 Unable to sign in using SSW Account (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#751 — 🐛 Azure DevOps MCP service name with spaces breaks approve flow (closed)
+- SSWConsulting/SSW.YakShaver#91 — ✨ Desktop App - Code Signing and Notarization (closed)
+- SSWConsulting/SSW.YakShaver#351 — ✨ Code signing & notarization - macOS (closed)
+- SSWConsulting/SSW.YakShaver#3024 — ✨ YakShaver 2.0 - Build and Release pipeline (closed)
+- SSWConsulting/SSW.YakShaver#3025 — ✨ YakShaver 2.0 - Auto update feature (closed)
+- SSWConsulting/SSW.YakShaver#332 — ✨ Add onboarding wizard (3-step) (closed)
+- SSWConsulting/SSW.YakShaver#444 — Wizard UX & onboarding improvements (closed)
+- SSWConsulting/SSW.YakShaver#442 — UX: Add preset MCP servers + onboarding checklist (closed)
+- SSWConsulting/SSW.YakShaver#3022 — ✨ YakShaver 2.0 - Canary Testing Document (closed)
+- SSWConsulting/SSW.YakShaver#470 — ✨ Factory Reset Button (closed)
+- SSWConsulting/SSW.YakShaver#3050 — ✨ Make UX of Desktop more user-friendly (closed)
+- SSWConsulting/SSW.YakShaver#3118 — ✨ YakShaver Desktop App Logo (closed)
+- SSWConsulting/SSW.YakShaver#3129 — Custom Prompt Settings with Tabs and Presets (closed)
+- SSWConsulting/SSW.YakShaver#3131 — ✨ YakShaver Desktop - Allow more flexible custom instructions (closed)
+- SSWConsulting/SSW.YakShaver#3047 — ✨ Environment Variables Accessibility (closed)
+- SSWConsulting/SSW.YakShaver#3095 — ✨ Secret Storage (closed)
+- SSWConsulting/SSW.YakShaver#3130 — Display Thought Process in Executing Tasks Area (closed)
+- SSWConsulting/SSW.YakShaver#3132 — Add YouTube URL and Timestamp to Issues (closed)
+- SSWConsulting/SSW.YakShaver#310 — Electron Backend - Add desktop app testing framework (closed)
+- SSWConsulting/SSW.YakShaver#311 — Portal Integration: send shave entries to API (closed)
+- SSWConsulting/SSW.YakShaver#312 — 🧠 Spike: Storing Desktop Shave Metadata (closed)
+- SSWConsulting/SSW.YakShaver#352 — Install ORM (Prisma/Drizzle) and prototype 'My Shave' (closed)
+- SSWConsulting/SSW.YakShaver#417 — 🕵️♂️ Spike - UI Refactor - Workflow progress panel (closed)
+- SSWConsulting/SSW.YakShaver#420 — 🐛 Thought process no longer shown (closed)
+- SSWConsulting/SSW.YakShaver#422 — ✨ Improve Login Flow for YakShaver Desktop (closed)
+- SSWConsulting/SSW.YakShaver#457 — Prompts, deduplication, title and emoji formatting differ (closed)
+- SSWConsulting/SSW.YakShaver#49 — Feature Requests for YakShaver Desktop (closed)
+- SSWConsulting/SSW.YakShaver#493 — Display play emoji and linked "Watch the video" on PBIs (closed)
+- SSWConsulting/SSW.YakShaver#51 — ✨ Version Display - Add Version Number to UI (closed)
+- SSWConsulting/SSW.YakShaver#515 — Detect macOS screen-record permission and instruct user (closed)
+- SSWConsulting/SSW.YakShaver#524 — UI: Use arrow next to AI reasoning/plan (closed)
+- SSWConsulting/SSW.YakShaver#64 — ♻️ Refactor Electron IPC Logic (closed)
+- SSWConsulting/SSW.YakShaver#119 — 🐛 Issue with Opening Links in Default Browser (closed)
+- SSWConsulting/SSW.YakShaver#152 — 🐛 Flashing 'GitHub token required' message (closed)
+- SSWConsulting/SSW.YakShaver#24 — ✨ Tray Icon Behavior - Modify app close to minimize to tray (closed)
+- SSWConsulting/SSW.YakShaver#307 — 🐛 default prompt not using template verbatim (closed)
+- SSWConsulting/SSW.YakShaver#3309 — 🐛 MCP Whitelist - Error on adding within execution loop (closed)
+
+### 🔌 3. MCP Server Registry, Gateway & Tool Integration — `product: desktop` (also `shared`)
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver.Desktop#720 — Dynamic MCP
+- SSWConsulting/SSW.YakShaver#2811 — ✨ YakShaver Agent 2.0 (MCP-tool portions)
+- SSWConsulting/SSW.YakShaver.Desktop#677 — ✨ YakShaver Desktop App (MCP server preset portions)
+
+**Scope:** The MCP plumbing surface — portal-side server registry/catalog, desktop-side dynamic fetching of tools, MCP gateway, OAuth for MCP servers, whitelist/approval UX, default/preset MCP servers (Jira, GitHub, Azure DevOps), and MCP-related bug fixes.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver.Desktop#721 — ✨ [PORTAL] - MCP Server Registry / Catalog (open)
+- SSWConsulting/SSW.YakShaver.Desktop#722 — ✨ [DESKTOP] - Fetch all available MCP tools from portal and persist (open)
+- SSWConsulting/SSW.YakShaver.Desktop#723 — ✨ [DESKTOP] - Create MCP Gateway (open)
+- SSWConsulting/SSW.YakShaver#2898 — ✨ Configure Default MCP Services for YakShaver Desktop (open)
+- SSWConsulting/SSW.YakShaver.Desktop#639 — ♻️ Move MCP common interfaces/types to shared folder (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#767 — ✨ Jira preset MCP server - Provide guided domain input (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#768 — ✨ Custom prompts - Default built-in MCP servers to selected (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#774 — ✨ Prompt Manager - Clarify default/template prompt (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#776 — ✨ Auto approve option for Project Prompt and MCP approvals (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#777 — 🤖✨ Project prompt selection - Improve confirmation wording (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#778 — 🤖✨ MCP tool confirmation - Show service icons (closed)
+- SSWConsulting/SSW.YakShaver.Desktop#779 — 🤖✨ Final result - Add spacing for camelCase titles (closed)
+- SSWConsulting/SSW.YakShaver#117 — 🕵🏻 SPIKE: Enhance YakShaver with Optional User Confirmation for MCP Actions (closed)
+- SSWConsulting/SSW.YakShaver#225 — ✨ Whitelist MCP Tool - Enhance YakShaver Desktop App (closed)
+- SSWConsulting/SSW.YakShaver#229 — ✨ Allow MCP Servers to connect with OAuth integration (closed)
+- SSWConsulting/SSW.YakShaver#2899 — ✨ Add MCP Support to YakShaver Desktop (closed)
+- SSWConsulting/SSW.YakShaver#2900 — ✨ Improve MCP Service Support and Error Messaging (closed)
+- SSWConsulting/SSW.YakShaver#2901 — ✨ Add Customizable Prompt Feature (closed)
+- SSWConsulting/SSW.YakShaver#2896 — ✨ Load MCP Server Configuration on Startup (closed)
+- SSWConsulting/SSW.YakShaver#2957 — ✨ YakShaver 2.0 MVP - MCP Host (closed)
+- SSWConsulting/SSW.YakShaver#3008 — ✨ YakShaver 2.0 - Local MCP Support - Implement STDIO (closed)
+- SSWConsulting/SSW.YakShaver#3010 — ✨ YakShaver 2.0 - Improve MCP Response Handling (closed)
+- SSWConsulting/SSW.YakShaver#637 — 🤖✨ Add fill_template MCP tool for LLM-powered template filling (closed)
+- SSWConsulting/SSW.YakShaver#20 — ✨ MCP and API Key Validation - Add Visual Confirmation (closed)
+
+### 💄 4. Portal UI/UX Polish & Visual Bug Fixes — `product: shared` (`teams` portal)
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#3063 — 💄 YakShaver Portal UI/UX improvements
+
+**Scope:** Visual fixes, alignment/padding, navigation indicators, button states, breadcrumbs, dashboards, project page tweaks, and small UX polish across the YakShaver client portal.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#1809 — ✨ Portal - UI Improvements (open)
+- SSWConsulting/SSW.YakShaver#2413 — ✨ Client Portal - Improve YouTube Integration UX (open)
+- SSWConsulting/SSW.YakShaver#2628 — ✨ Portal - Active State Indication for Nav Links (open)
+- SSWConsulting/SSW.YakShaver#2779 — 💄 Portal - Improvements to "New Project" form (open)
+- SSWConsulting/SSW.YakShaver#2993 — ✨ Admin Visibility Enhancement (open)
+- SSWConsulting/SSW.YakShaver#2997 — 💄 MyShaves UX - Improve status colour variations (open)
+- SSWConsulting/SSW.YakShaver#3014 — 🐛 Bug - YakShaver favicon is squished (open)
+- SSWConsulting/SSW.YakShaver#3015 — ✨ UI Filter Indicator - Active filters (open)
+- SSWConsulting/SSW.YakShaver#3016 — 🐛 Bug - Inconsistent Titles in Portal (open)
+- SSWConsulting/SSW.YakShaver#3017 — 🐛 Bug - Last Updated Column Cut Off (open)
+- SSWConsulting/SSW.YakShaver#3018 — 🐛 Bug - Padding/Alignment in Integrations (open)
+- SSWConsulting/SSW.YakShaver#3019 — ✨ Video Thumbnail Overlay (open)
+- SSWConsulting/SSW.YakShaver#3026 — ✨ UI Improvement - Add Hover State for Clickable Tools (open) — *flagged in Section 2*
+- SSWConsulting/SSW.YakShaver#3033 — ✨ Shorten Backlog URL - Click Functionality (open)
+- SSWConsulting/SSW.YakShaver#3038 — 🐛 Bug - Duplicate Navigation to Dashboard (open)
+- SSWConsulting/SSW.YakShaver#3039 — ✨ Subscription Page Button Colors (open)
+- SSWConsulting/SSW.YakShaver#3040 — 🐛 Bug - Sorting in My Shaves Table (open)
+- SSWConsulting/SSW.YakShaver#3045 — ♻️ UI/UX - Dashboard view (open)
+- SSWConsulting/SSW.YakShaver#3051 — ✨ Menu Reorganization - Settings Layout (open)
+- SSWConsulting/SSW.YakShaver#3057 — ✨ Project Interface (open)
+- SSWConsulting/SSW.YakShaver#3058 — ✨ Dashboard and Menu Improvements (open)
+- SSWConsulting/SSW.YakShaver#3151 — ✨ UI Alignment - Field and Label Layout (open)
+- SSWConsulting/SSW.YakShaver#3152 — ✨ Dashboard - Date Range and Metrics Display (open)
+- SSWConsulting/SSW.YakShaver#3320 — 🐛 Bug - Breadcrumb and Route Inconsistencies (open)
+- SSWConsulting/SSW.YakShaver#2627 — ✨ Improve Project Deletion Process (closed)
+- SSWConsulting/SSW.YakShaver#2991 — ✉️ portal 1/2 wide fields (closed)
+- SSWConsulting/SSW.YakShaver#3034 — 💄 Move "Delete Project" Button (closed)
+- SSWConsulting/SSW.YakShaver#3069 — ✨ Project Page Updates (closed)
+- SSWConsulting/SSW.YakShaver#3232 — ✨ Leaderboard - Most shaved Projects (closed)
+- SSWConsulting/SSW.YakShaver#3481 — 🐛 OneDrive subscription dips (closed)
+- SSWConsulting/SSW.YakShaver#3495 — 🤖 System Info button style inconsistency (closed)
+- SSWConsulting/SSW.YakShaver#3496 — 🐛 System Info visible without access (closed)
+
+### 🏢 5. Client Tenant Portal — Multi-tenancy, Admin & Project Management — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#693 — 🚀 Client Tenant Setup (including admin portal)
+
+**Scope:** The backend and admin-facing portal capabilities: multi-tenancy infrastructure, project sync from DevOps/GitHub, user roles (promote/demote/revoke), tenant invitations, admin notices, dashboard endpoints (Recent Shaves, Adoption Rate, Leaderboard backend), system-portal config (AI/STT model toggles, prompt editing, user prefs).
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#1187 — ✨ Administrator Permission Notice on GitHub Integration (open)
+- SSWConsulting/SSW.YakShaver#1265 — ✨ Work Item Dashboard (open)
+- SSWConsulting/SSW.YakShaver#1411 — 🕵🏻 Spike: Deleting a project in DevOps/GitHub (open)
+- SSWConsulting/SSW.YakShaver#1412 — ✨ Implement Logging (open)
+- SSWConsulting/SSW.YakShaver#1497 — ✨ Sync Projects - exclude projects through portal (open)
+- SSWConsulting/SSW.YakShaver#1716 — ✨ New Tenant: Invite Users (open)
+- SSWConsulting/SSW.YakShaver#1737 — 💄 Frontend - Recent Shaves Module (open)
+- SSWConsulting/SSW.YakShaver#1738 — ✨ Backend - distinguish user by Role (open)
+- SSWConsulting/SSW.YakShaver#1747 — ✨ Backend - User Roles - Promotion and Demotion (open)
+- SSWConsulting/SSW.YakShaver#1748 — 💄 Frontend - Hide content based on roles (open)
+- SSWConsulting/SSW.YakShaver#1749 — 💄 Frontend - User Roles - Revoke/Demote (open)
+- SSWConsulting/SSW.YakShaver#1755 — ✨ Backend - Endpoint for Recent Shaves Module (open)
+- SSWConsulting/SSW.YakShaver#1838 — ✨ Backend - Adoption Rate Formula (open)
+- SSWConsulting/SSW.YakShaver#81 — ✨ Client Portal - User preference (notification opt-in) (open)
+- SSWConsulting/SSW.YakShaver#911 — ✨ Support: OneDrive, GoogleDrive, ICloud, Dropbox storage (open)
+- SSWConsulting/SSW.YakShaver#1007 — ✨ Multi Tenancy - Master Database Design (closed)
+- SSWConsulting/SSW.YakShaver#1008 — ✨ Multi Tenancy - REST API for Tenants (closed)
+- SSWConsulting/SSW.YakShaver#1009 — ✨ Multi Tenancy - DevOps CD Pipeline Provision DB (closed)
+- SSWConsulting/SSW.YakShaver#1010 — ✨ Multi Tenancy - Auto-Apply EF Migrations (closed)
+- SSWConsulting/SSW.YakShaver#1011 — ✨ Multi Tenancy - Dynamic Database Connection String (closed)
+- SSWConsulting/SSW.YakShaver#1037 — ✨ Provision Master Database CI/CD (closed)
+- SSWConsulting/SSW.YakShaver#1076 — ✨ Configure Azure DevOps Service Integrations (closed)
+- SSWConsulting/SSW.YakShaver#1109 — ✨ Register domain or subdomain for Portal/Api (closed)
+- SSWConsulting/SSW.YakShaver#1110 — ✨ Create Staging YakShaver App on GitHub (closed)
+- SSWConsulting/SSW.YakShaver#1129 — ✨ Client Portal - Entra ID Integration (closed)
+- SSWConsulting/SSW.YakShaver#1153 — ✨ Set up client portal for production (closed)
+- SSWConsulting/SSW.YakShaver#1182 — ✨ Add a product tour wizard to the portal (closed)
+- SSWConsulting/SSW.YakShaver#1186 — ✨ Improve UI/UX Design (closed)
+- SSWConsulting/SSW.YakShaver#1366 — ✨ Implement Video Tracking and Retry Feature (closed)
+- SSWConsulting/SSW.YakShaver#1518 — ✨ My Shaves list (closed)
+- SSWConsulting/SSW.YakShaver#1734 — 💄 Frontend - Dashboard - Leaderboard (closed)
+- SSWConsulting/SSW.YakShaver#1743 — ✨ Backend - Endpoints for Leaderboard (closed)
+- SSWConsulting/SSW.YakShaver#1745 — ✨ Backend - Update table to include Roles (closed)
+- SSWConsulting/SSW.YakShaver#270 — ✨ API Security Enhancement for PROD (closed)
+- SSWConsulting/SSW.YakShaver#2931 — 🐛 Reprocessing of Completed YakShaver Tasks (closed)
+- SSWConsulting/SSW.YakShaver#3072 — ✨ My Tenant - See other users (closed)
+- SSWConsulting/SSW.YakShaver#3279 — ✨ Org Member Visibility - Add members from other orgs (closed)
+- SSWConsulting/SSW.YakShaver#3519 — 🐛 Subscription credits not resetting on renewal (closed)
+- SSWConsulting/SSW.YakShaver#412 — 📝 Check function Authorization level (closed)
+- SSWConsulting/SSW.YakShaver#434 — ♻️ Replace Container App with Function App (closed)
+- SSWConsulting/SSW.YakShaver#621 — ⚗️ Observability - Set up SEQ (closed)
+- SSWConsulting/SSW.YakShaver#711 — 👷 Stable AI API Outputs for CI/CD Tests (closed)
+- SSWConsulting/SSW.YakShaver#712 — 🐛 Missing Telemetry Data (closed)
+- SSWConsulting/SSW.YakShaver#73 — ✨ Video to YouTube (already on MS Stream) (closed)
+- SSWConsulting/SSW.YakShaver#759 — 🔍 Spike - Set up Client Instance (closed)
+- SSWConsulting/SSW.YakShaver#770 — ✨ System Portal - CI/CD Pipeline (closed)
+- SSWConsulting/SSW.YakShaver#78 — ✨ Configure GitHub Service Integrations (closed)
+- SSWConsulting/SSW.YakShaver#79 — ✨ Client Portal - CI/CD Pipeline (closed)
+- SSWConsulting/SSW.YakShaver#798 — 📝 ADR - Tech Stack for Portals (closed)
+- SSWConsulting/SSW.YakShaver#816 — 🔍 Spike - cost effective global toggle (closed)
+- SSWConsulting/SSW.YakShaver#819 — 🔎 Spike - Multi-Tenancy Solution (closed)
+- SSWConsulting/SSW.YakShaver#913 — ✨ Frontend - Initial Project setup (closed)
+- SSWConsulting/SSW.YakShaver#914 — ✨ Frontend - Tech stack Decision + Rule (closed)
+- SSWConsulting/SSW.YakShaver#933 — ✨ Automated YakShaver access script (closed)
+- SSWConsulting/SSW.YakShaver#997 — ✨ Backend Authentication (closed)
+
+### ♻️ 6. Auth Migration — IdentityServer, OAuth & Invitations — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#3494 — ♻️ Auth Migration
+- SSWConsulting/SSW.YakShaver#2811 — OAuth-related portion
+
+**Scope:** SSW IdentityServer migration, Better-Auth spike & ADR, Entra optionality wizard, OAuth2 + PKCE flow, refresh-token plumbing across desktop/portal/backend, dynamic client registration, invitation system for non-Entra users.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#3509 — ✨ Invitation system for non-Entra users (open)
+- SSWConsulting/SSW.YakShaver#578 — Backend token exchange (auth code → access + refresh tokens) (open)
+- SSWConsulting/SSW.YakShaver#2889 — ✨ External Login Integration - Microsoft or GitHub accounts (open)
+- SSWConsulting/SSW.YakShaver#3503 — ✨ Make Entra permission optional on wizard (closed)
+- SSWConsulting/SSW.YakShaver#3504 — ✨ Spike: Break down 'Make Entra permission optional' (closed)
+- SSWConsulting/SSW.YakShaver#3505 — ✨ Spike: Investigate Better-Auth integration (closed)
+- SSWConsulting/SSW.YakShaver#3506 — ✨ Revisit ADR after Better-Auth Spike (closed)
+- SSWConsulting/SSW.YakShaver#3507 — Breakdown authentication implementation approach (closed)
+- SSWConsulting/SSW.YakShaver#3508 — ✨ Policy for assigning tenant IDs to non-Entra users (closed)
+- SSWConsulting/SSW.YakShaver#3597 — ✨ Desktop app - Authentication using SSW IdentityServer (closed)
+- SSWConsulting/SSW.YakShaver#3598 — ✨ Portal - Authentication using SSW IdentityServer (closed)
+- SSWConsulting/SSW.YakShaver#3599 — ✨ Backend API - Validate access tokens using IdentityServer (closed)
+- SSWConsulting/SSW.YakShaver#297 — Replace in-memory OAuth provider with persistent OAuth (closed)
+- SSWConsulting/SSW.YakShaver#305 — Feature: Add sign-in flow to YakShaver Desktop (deep links) (closed)
+- SSWConsulting/SSW.YakShaver#308 — 🧠 Spike: Secure OAuth2 Flow with PKCE or BFF Token Broker (closed)
+- SSWConsulting/SSW.YakShaver#577 — Implement simple 302 OAuth authorize redirect (closed)
+- SSWConsulting/SSW.YakShaver#579 — Store access and refresh tokens in Electron safe storage (closed)
+- SSWConsulting/SSW.YakShaver#580 — Implement refresh access token flow (closed)
+- SSWConsulting/SSW.YakShaver#581 — Persist dynamic client registration per server name (closed)
+- SSWConsulting/SSW.YakShaver#624 — ✨ OAuth Flow - Desktop initiates OAuth with Backend (closed)
+- SSWConsulting/SSW.YakShaver#630 — 🐛 OAuth callback returns empty page instead of deep-link (closed)
+- SSWConsulting/SSW.YakShaver#636 — ✨ YouTube Integration - Authorization + PKCE (closed)
+- SSWConsulting/SSW.YakShaver#3378 — Backend: Move discovery lookup from Desktop to .NET API (closed)
+- SSWConsulting/SSW.YakShaver#300 — UI: Reorder Tool Approval Mode (closed)
+
+### 💳 7. Subscriptions, Pricing & Billing Emails — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#693 (subscription email portion)
+- SSWConsulting/SSW.YakShaver#694 (pricing/licensing portion)
+
+**Scope:** Customer-facing subscription lifecycle: tier confirmation emails (starter/team/growth/enterprise), addon purchase emails, cancellation emails, renewal reminders, trial-expiring nudges, plus the website pricing/licensing pages they reference.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#1750 — ✨ Email confirming subscription tier (open)
+- SSWConsulting/SSW.YakShaver#1751 — ✨ Email confirming addon purchase (open)
+- SSWConsulting/SSW.YakShaver#1752 — ✨ Email confirming cancellation (open)
+- SSWConsulting/SSW.YakShaver#1756 — ✨ Email subscription renewal reminder (open)
+- SSWConsulting/SSW.YakShaver#1757 — ✨ Email Trial expiring → user will be charged (open)
+- SSWConsulting/SSW.YakShaver#317 — ✨ Website - Add a licencing model (closed)
+- SSWConsulting/SSW.YakShaver#716 — ✨ Add pricing model to website (closed)
+- SSWConsulting/SSW.YakShaver#787 — ✨ Website - Create Pricing Page Component (closed)
+
+### 🌐 8. Public/Free YakShaver & Browser Extension — `product: shared` (extension + portal)
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#2308 — 🚀 Free YakShaver for Public Use - MVP
+
+**Scope:** The free/public tier: GitHub-login authentication for public users, public-mode behavior (no name recognition), watermark (static + dynamic), public YouTube hosting, browser extension with GitHub login, retry/upload UX, public/private repo status display.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#2313 — ✨ Public - Users not supported, ignore names in video (open)
+- SSWConsulting/SSW.YakShaver#2314 — ✨ Public - Notifications - 1-way email (open)
+- SSWConsulting/SSW.YakShaver#2315 — ✨ Public - Watermark - Dynamic Video branding (open)
+- SSWConsulting/SSW.YakShaver#2324 — ✨ Public - Open Source GitHub Repo + YouTube free (open)
+- SSWConsulting/SSW.YakShaver#2440 — ✨ Public - Enhance Browser Extension Auth and UX (open)
+- SSWConsulting/SSW.YakShaver#2702 — ♻️ Extension - Integrate "ExecutePromptCommand" (open)
+- SSWConsulting/SSW.YakShaver#2708 — 🐛 Retry Feature Fails for Videos from Chrome Extension (open)
+- SSWConsulting/SSW.YakShaver#2744 — ✨ Display Public/Private Status of Repositories (open)
+- SSWConsulting/SSW.YakShaver#1971 — ✨ Public - Watermark - Static Video branding (closed)
+- SSWConsulting/SSW.YakShaver#2310 — ✨ Public - Authentication - FREE, GitHub login (closed)
+- SSWConsulting/SSW.YakShaver#2311 — ✨ Public - Projects - supply list of public repos (closed)
+- SSWConsulting/SSW.YakShaver#2312 — ✨ Public - Video Host - YouTube Channel (closed)
+- SSWConsulting/SSW.YakShaver#2322 — ✨ Public - Spike Browser Extension GitHub Auth (closed)
+- SSWConsulting/SSW.YakShaver#2542 — ✨ Update Wizard for GitHub Users (closed)
+- SSWConsulting/SSW.YakShaver#2543 — ✨ Support GitHub Users in Video Endpoint (closed)
+- SSWConsulting/SSW.YakShaver#2544 — ✨ Chrome Extension - Add GitHub Login UI (closed)
+- SSWConsulting/SSW.YakShaver#2563 — ✨ Watermark - Push message to service bus (closed)
+- SSWConsulting/SSW.YakShaver#2668 — 🐛 Browser ext - Fix uploads for GitHub auth (closed)
+- SSWConsulting/SSW.YakShaver#2684 — ✨ Watermark - Container App processing logic (closed)
+- SSWConsulting/SSW.YakShaver#2703 — 💄 Extension - Improve UI (closed)
+
+### 🌎 9. Chinafy / i18n — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#2550 — 🌎 YakShaver Chinafy
+
+**Scope:** China-specific support: Chinese-language portal, Chinese chrome extension (Tencent/Baidu/Alibaba browsers), BiliBili video host provider, Chinese-language PBI generation.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#2606 — 🌎 ✨ Chinese browser extension (Tencent/Baidu/Alibaba) (open)
+- SSWConsulting/SSW.YakShaver#2619 — 🌎 ✨ Chinese Language Support to Portal (open)
+- SSWConsulting/SSW.YakShaver#2631 — 🌎 ✨ BiliBili Part 2 - test BiliBiliVideoHostProvider (open)
+- SSWConsulting/SSW.YakShaver#2548 — 🌎 Draft v2 PowerPoint Presentation (closed)
+- SSWConsulting/SSW.YakShaver#2592 — 🌎 ✨ Chinese Language Support for PBI Generation (closed)
+- SSWConsulting/SSW.YakShaver#2593 — 🌎 🐛 Chrome Extension Not Working (closed)
+- SSWConsulting/SSW.YakShaver#2594 — 🌎 ✨ Integrate GitHub Login (Staging only) (closed)
+- SSWConsulting/SSW.YakShaver#2595 — 🌎 ✨ BiliBili Part 1 - Implement provider (closed)
+- SSWConsulting/SSW.YakShaver#2596 — 🌎 ✨ Alternative Chrome Extension Download instructions for China (closed)
+- SSWConsulting/SSW.YakShaver#2597 — 🌎 ✨ Chinese Language to Chrome Extension (closed)
+- SSWConsulting/SSW.YakShaver#2598 — 🌎 ✨ Chinese Language to YakShaver.ai (closed)
+
+### 🔗 10. External Work Item Systems Integration — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#633 — 🚀 Work item systems other than GitHub and AzDO
+
+**Scope:** Adding support for Linear, Jira, Zendesk, and other work-item repositories beyond GitHub/Azure DevOps. Includes shared API endpoint plumbing.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#1767 — ✨ Integrate YakShaver with Linear (open)
+- SSWConsulting/SSW.YakShaver#510 — ✨ Integrate Zendesk Ticket Creation (open)
+- SSWConsulting/SSW.YakShaver#562 — ✨ Integrate YakShaver with Jira (open) — *flagged in Section 2*
+- SSWConsulting/SSW.YakShaver#593 — 🚀 Gold Plating | API Endpoints (open)
+- SSWConsulting/SSW.YakShaver#549 — ✨ Duplicates - Scan existing backlog before creating PBI (closed)
+
+### 🌐 11. Marketing Website & SEO — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#694 — 🚀 CX - Website, pricing, etc.
+- SSWConsulting/SSW.YakShaver#2441 — ✨ Website - Improve SEO using Semrush
+
+**Scope:** YakShaver.ai marketing site — pages, components (hero/CTAs/FAQ/footer/blog), tagline, logo, mobile responsiveness, plus SEO improvements (Semrush-driven schema, keywords, backlinks, readability).
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#519 — ✨ Website - Onboarding experience (open)
+- SSWConsulting/SSW.YakShaver#616 — ✨ YakShaver.ai - SEO Review (open)
+- SSWConsulting/SSW.YakShaver#635 — ✨ YouTube Channel - Upload Previous Videos (open)
+- SSWConsulting/SSW.YakShaver#714 — ✨ Improve UX for YakShaver Plugin Activation (open)
+- SSWConsulting/SSW.YakShaver#793 — ✨ Bring Your Own Infrastructure CX (open)
+- SSWConsulting/SSW.YakShaver#2443 — ✨ Website - List google result as Software Application (open)
+- SSWConsulting/SSW.YakShaver#2444 — ✨ Website - Google video embed initial render (open)
+- SSWConsulting/SSW.YakShaver#2445 — ✨ Website - Flesch-Kincaid readability (open)
+- SSWConsulting/SSW.YakShaver#2446 — ✨ Website - Include related keywords (open)
+- SSWConsulting/SSW.YakShaver#2447 — ✨ Website - Obtain high quality backlinks (open)
+- SSWConsulting/SSW.YakShaver#2448 — ✨ Website - Include more written content (open)
+- SSWConsulting/SSW.YakShaver#1041 — 📝 Website - blog .mdx format (closed)
+- SSWConsulting/SSW.YakShaver#1259 — ✨ Website - everyone can edit (closed)
+- SSWConsulting/SSW.YakShaver#1276 — ✨ Update Default Video to Short (closed)
+- SSWConsulting/SSW.YakShaver#500 — 📝 Docs - How to use YakShaver (closed)
+- SSWConsulting/SSW.YakShaver#547 — 📝 SugarLearning Onboard SSW Users (closed)
+- SSWConsulting/SSW.YakShaver#548 — ✨ Teams Bot - Improve Welcome Message (closed)
+- SSWConsulting/SSW.YakShaver#672 — ♻️ Product Page - Move to TinaCMS (closed)
+- SSWConsulting/SSW.YakShaver#702 — 💄 Marketing Page - UX Research (closed)
+- SSWConsulting/SSW.YakShaver#703 — 💄 Marketing Page - Design Mockups (closed)
+- SSWConsulting/SSW.YakShaver#751 — ✨ Update Logo Icon (closed)
+- SSWConsulting/SSW.YakShaver#778 — ✨ Reusable Hero Section Component (closed)
+- SSWConsulting/SSW.YakShaver#779 — ✨ Implement CTAs (closed)
+- SSWConsulting/SSW.YakShaver#780 — ✨ Tabbed Feature Section (closed)
+- SSWConsulting/SSW.YakShaver#781 — ✨ Video Support to About Us (closed)
+- SSWConsulting/SSW.YakShaver#782 — ✨ Mobile Responsive (closed)
+- SSWConsulting/SSW.YakShaver#783 — ✨ Blog Inner Page (closed)
+- SSWConsulting/SSW.YakShaver#784 — ✨ FAQs Component (closed)
+- SSWConsulting/SSW.YakShaver#785 — ✨ Reusable Banner Component (closed)
+- SSWConsulting/SSW.YakShaver#786 — ✨ Footer with Copyright + Social (closed)
+- SSWConsulting/SSW.YakShaver#788 — ✨ Blog Index Page (closed)
+- SSWConsulting/SSW.YakShaver#789 — ✨ Sticky Navbar (closed)
+- SSWConsulting/SSW.YakShaver#810 — ✨ Update YakShaver Tagline (closed)
+- SSWConsulting/SSW.YakShaver#813 — 💄 Product Page - Inter webfont (closed)
+- SSWConsulting/SSW.YakShaver#2442 — ✨ Use keywords in titles (closed)
+
+### 💬 12. Teams Bot — UX, Disambiguation & PBI Quality — `product: teams`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#584 — 🚀 Improvements asked for by SSW staff (MVP)
+- SSWConsulting/SSW.YakShaver#720 — 🚀 Improvements asked for by SSW staff (nice to have)
+
+**Scope:** The Teams bot experience — disambiguation cards, prompt-quality fixes, OpenAI prompt tuning, Teams UI affordances (cancel buttons, mention/CC, leaderboard cards, recording status, disambiguation drop-downs, "Move To" buttons), PBI/email format quality, name/project spelling, and small staff-driven workflow improvements.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#846 — ✨ Teams App - Exclude YakShaver User from Search Results (open)
+- SSWConsulting/SSW.YakShaver#5 — ✨ Azure DevOps - custom templates per client (open)
+- SSWConsulting/SSW.YakShaver#623 — ✨ Naughty Words Filter (open) — *flagged in Section 2*
+- SSWConsulting/SSW.YakShaver#647 — ✨ What happens when you turn it off mid-shave? (open)
+- SSWConsulting/SSW.YakShaver#67 — ✨ Ask for a Followupthen (open)
+- SSWConsulting/SSW.YakShaver#683 — ✨ Add 'Start Yakking' Button in Chat Window (open)
+- SSWConsulting/SSW.YakShaver#70 — ✨ GitHub - Fetch issue templates (open)
+- SSWConsulting/SSW.YakShaver#704 — ✨ Teams Bot UI - Remove "Present Now" + improve View Prompt (open)
+- SSWConsulting/SSW.YakShaver#84 — ✨ Done emails - Sent by YakShaver (open)
+- SSWConsulting/SSW.YakShaver#980 — ✨ Coaching Feedback Feature for Video Submissions (open)
+- SSWConsulting/SSW.YakShaver#1135 — ✨ Add Link to Video in Notification (closed)
+- SSWConsulting/SSW.YakShaver#357 — ✨ Implement Name Search with CRM fuzzy search (closed)
+- SSWConsulting/SSW.YakShaver#464 — ✨ Set up YakShaver themselves on teams via bot (closed)
+- SSWConsulting/SSW.YakShaver#487 — ✨ Video title should not be a GUID (closed)
+- SSWConsulting/SSW.YakShaver#511 — ✨ Email and PBI prompts - implementation (closed)
+- SSWConsulting/SSW.YakShaver#523 — ✨ Email generation - iterate (closed)
+- SSWConsulting/SSW.YakShaver#532 — ✨ Ignore Historical Projects (closed)
+- SSWConsulting/SSW.YakShaver#534 — ✨ Update Email Notification Card (closed)
+- SSWConsulting/SSW.YakShaver#553 — ✨ Disambiguation - Cancel button (closed)
+- SSWConsulting/SSW.YakShaver#563 — 🐛 Disambiguation - Blank Drop-Down (closed)
+- SSWConsulting/SSW.YakShaver#567 — ✨ Email HTML formating (closed)
+- SSWConsulting/SSW.YakShaver#577 — 🐛 OpenAI Prompt - Invalid JSON in Prod (closed) *(note: 577 is shared with cluster 6 by number, distinct title here)*
+- SSWConsulting/SSW.YakShaver#580 — ♻️ Disambiguation Drop-Down (closed)
+- SSWConsulting/SSW.YakShaver#581 — 🐛 OK Button error (closed)
+- SSWConsulting/SSW.YakShaver#582 — ✨ CC/@mention for Teams call participants (closed)
+- SSWConsulting/SSW.YakShaver#585 — 🧪 Canary Deployment - Mark /10 (closed)
+- SSWConsulting/SSW.YakShaver#589 — ♻️ Improve Initial Decision Prompt (closed)
+- SSWConsulting/SSW.YakShaver#591 — ♻️ Improve Email Task Style (closed)
+- SSWConsulting/SSW.YakShaver#594 — 👷 DevOps - Update Purchase Budget (closed)
+- SSWConsulting/SSW.YakShaver#599 — ✨ Email Card Readability + CC Field (closed)
+- SSWConsulting/SSW.YakShaver#608 — ✨ Structured JSON Outputs for OpenAI (closed)
+- SSWConsulting/SSW.YakShaver#609 — ✨ Combine Toggle for Private and Channel Calls (closed)
+- SSWConsulting/SSW.YakShaver#624 — ✨ User Notification for Recording Processing (closed)
+- SSWConsulting/SSW.YakShaver#629 — ♻️ Azure DevOps PBI - Formatting (closed)
+- SSWConsulting/SSW.YakShaver#638 — ♻️ Improve Toggle Messages (closed)
+- SSWConsulting/SSW.YakShaver#639 — 🐛 Couldn't figure out who "myself" is (closed)
+- SSWConsulting/SSW.YakShaver#657 — ✨ Explicit Assignment Logic for PBIs (closed)
+- SSWConsulting/SSW.YakShaver#664 — ✨ Add 'Move To' Button (closed)
+- SSWConsulting/SSW.YakShaver#680 — 🐛 PBI description after selected project (closed)
+- SSWConsulting/SSW.YakShaver#685 — ✨ Gamification - Leaderboard card (closed)
+- SSWConsulting/SSW.YakShaver#690 — 🐛 CodingChallenge - sending email not PBI (closed)
+- SSWConsulting/SSW.YakShaver#701 — ✨ Disambiguation - "Cancel" clearer (closed)
+- SSWConsulting/SSW.YakShaver#705 — ✨ Updating welcome message (closed)
+- SSWConsulting/SSW.YakShaver#749 — 🐛 Mentioning the wrong Users (closed)
+- SSWConsulting/SSW.YakShaver#750 — ✨ Correct Spelling of Project Names (closed)
+- SSWConsulting/SSW.YakShaver#768 — 🐛 Recording boot delay → incomplete prompt (closed)
+- SSWConsulting/SSW.YakShaver#804 — ✨ Notify User When YakShaver is Ready (closed)
+- SSWConsulting/SSW.YakShaver#817 — 🐛 YakShaver not working on production (closed)
+- SSWConsulting/SSW.YakShaver#818 — ✨ Inject Project List as Context (closed)
+- SSWConsulting/SSW.YakShaver#820 — 🐛 Expected an Email but PBI Was Created (closed)
+- SSWConsulting/SSW.YakShaver#855 — 💄 UX: Improve MoveTo (closed)
+- SSWConsulting/SSW.YakShaver#858 — ✨ Name Correction and User Notification (closed)
+- SSWConsulting/SSW.YakShaver#938 — ✨ 'Move Work Item' Button on Email Card (closed)
+- SSWConsulting/SSW.YakShaver#1022 — ✨ Whisper API File Size Limit (closed)
+- SSWConsulting/SSW.YakShaver#1193 — ✨ GitHub Integration 🟩 (closed)
+- SSWConsulting/SSW.YakShaver#282 — ✨ Sprint Review/Forecast emails (closed)
+- SSWConsulting/SSW.YakShaver#328 — ✨ Screenshot - send to ChatGPT for URL (closed)
+- SSWConsulting/SSW.YakShaver#342 — ✨ Multiple iterations across orgs (closed)
+- SSWConsulting/SSW.YakShaver#346 — ✨ Add Task Management to GitHub Issues (closed)
+- SSWConsulting/SSW.YakShaver#426 — ✨ Correct Transcription for PBI/Email (closed)
+- SSWConsulting/SSW.YakShaver#433 — ✨ Endpoint to Delete Test PBIs (closed)
+- SSWConsulting/SSW.YakShaver#516 — ✨ Teams Bot Notification for Recording (closed)
+- SSWConsulting/SSW.YakShaver#524 — ✨ Brainstorming discussions (closed) *(distinct from Desktop #524)*
+- SSWConsulting/SSW.YakShaver#600 — 🐛 Video Rename Fails Slash (closed)
+- SSWConsulting/SSW.YakShaver#620 — ✨ Cancel Command Recognition (closed)
+- SSWConsulting/SSW.YakShaver#630 — ✨ Teams Bot UI - PBI Number (closed) *(distinct from #630 in cluster 6)*
+- SSWConsulting/SSW.YakShaver#634 — ✨ YouTube Channel - Set up (closed)
+- SSWConsulting/SSW.YakShaver#658 — ✨ Only Relevant People in PBIs (closed)
+- SSWConsulting/SSW.YakShaver#663 — ✨ Remove Description Field (closed)
+- SSWConsulting/SSW.YakShaver#671 — 🐛 Chinese → email instead of PBI (closed) *(distinct from Desktop#671)*
+- SSWConsulting/SSW.YakShaver#678 — ✨ Automate Daily Scrum Summary Email (closed)
+- SSWConsulting/SSW.YakShaver#684 — ✨ Improve Confirmation Message when YakShaver On (closed)
+- SSWConsulting/SSW.YakShaver#692 — ✨ Transcription Accuracy for Email CC (closed)
+- SSWConsulting/SSW.YakShaver#709 — ✨ Enhance Screenshot Functionality (closed)
+- SSWConsulting/SSW.YakShaver#710 — ✨ Enhance Contact Matching Logic (closed)
+- SSWConsulting/SSW.YakShaver#742 — ✨ Leaderboard - timeframe (closed)
+- SSWConsulting/SSW.YakShaver#744 — ✨ AI-Driven Approval System (closed)
+- SSWConsulting/SSW.YakShaver#756 — ✨ Leaderboard - by projects (closed)
+- SSWConsulting/SSW.YakShaver#764 — 🐛 Correct Spelling of Project Name (closed)
+- SSWConsulting/SSW.YakShaver#802 — 🐛 Repeated Welcome Message (closed)
+- SSWConsulting/SSW.YakShaver#805 — ✨ Prompt user for email recipient when unknown (closed)
+- SSWConsulting/SSW.YakShaver#814 — ✨ Multi-video queue notification (closed)
+- SSWConsulting/SSW.YakShaver#854 — 🕵🏻 Spike - UI/UX in Teams (closed)
+
+### 📊 13. Reporting & Analytics — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#2353 — 🚀 Reporting and YakShaver Stats
+
+**Scope:** Usage analytics, project-inference confidence reporting, individual usage trends, log-analytics cost optimization.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#2354 — ♻️ Process Step logging - "MOVED TO" actions (open)
+- SSWConsulting/SSW.YakShaver#2355 — ✨ Project Inference and confidence levels (open)
+- SSWConsulting/SSW.YakShaver#2456 — ✨ Display trends on individual usage (open)
+- SSWConsulting/SSW.YakShaver#2838 — ✨ Optimize Log Analytics Costs (closed)
+
+### 🚀 14. YakShaver 360 — Initial Build — `product: 360`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#3595 — 🚀 YakShaver 360 - Frontend
+- SSWConsulting/SSW.YakShaver#3596 — 🚀 YakShaver 360 - Backend
+
+**Scope:** Bring up the YakShaver 360 product — initial Next.js frontend project setup, .NET backend project setup, and supporting infrastructure for both. This cluster supersedes the two parent epics, which are essentially containers for the same four PBIs.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#3604 — ✨ YakShaver 360 - Initial .NET Project Set Up (open)
+- SSWConsulting/SSW.YakShaver#3605 — ✨ YakShaver 360 - Initial Next.js Project Set Up (open)
+- SSWConsulting/SSW.YakShaver#3606 — ✨ YakShaver 360 - Backend Infrastructure (open)
+- SSWConsulting/SSW.YakShaver#3607 — ✨ YakShaver 360 - Frontend Infrastructure (open)
+
+### 🏢 15. Client #1 Onboarding (Consulting Scenario) — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#2012 — 🚀 Client #1 - Onboarding (Consulting Scenario)
+
+**Scope:** First paying-client onboarding scenario — using a consulting-engagement client's own SharePoint folder to host their videos.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#2017 — ✨ Client #1 - Video on Client's SharePoint folder (open)
+
+### ♻️ 16. Architecture Refactor — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#2188 — ♻️ SSW.YakShaver
+
+**Scope:** Cross-cutting backend refactors — currently only the marker-interface refactor of AdminDbContext / TenantDbContext.
+
+**PBIs:**
+- SSWConsulting/SSW.YakShaver#2189 — ♻️ Refactor AdminDbContext and TenantDbContext to use Marker Interfaces (open)
+
+### 🏍️ 17. Performance Optimization — `product: shared`
+
+**Consolidates PBIs from:**
+- SSWConsulting/SSW.YakShaver#3583 — ✨ 🏍️ Performance Optimization
+
+**Scope:** Empty epic — has 0 sub-issues. Either populate it with concrete perf-tuning PBIs (LLM cost, video processing throughput, portal cold-start) or close it as a placeholder. (uncertain — listed for transparency, also flagged in Section 3.)
+
+**PBIs:** _none_
+
+---
+
+## 2. Duplicate candidates (whole backlog)
+
+> Curated from a 875×3089 title-similarity scan. Series patterns ("Sprint TBD video to YouTube", per-person "Canary Testing - ") and sequence pairs ("Phase N" vs "Phase N+1") were excluded as known templates, not duplicates. Each open issue here is on the left.
+
+| Open issue | Matched issue (state) | Reason |
+|------------|------------------------|--------|
+| SSWConsulting/SSW.YakShaver#3349 — ✨ Download Page - Dedicated OS-specific installer page | SSWConsulting/SSW.YakShaver.Desktop#492 — same title (open) | exact-title cross-repo dup; pick one home repo |
+| SSWConsulting/SSW.YakShaver#3168 — ✨ Column Selector - Replace Pascal Case with Full Name | SSWConsulting/SSW.YakShaver#3167 — same title (closed) | exact title; appears already done in #3167 |
+| SSWConsulting/SSW.YakShaver#3142 — 🐛 Bug - Video Duration Mismatch | SSWConsulting/SSW.YakShaver#1919 — same bug "in YakShaver" (closed) | same root bug, regression or stale dup of #1919/#2800 |
+| SSWConsulting/SSW.YakShaver#3081 — ✨ Profile Icon Redundancy → editable user details | SSWConsulting/SSW.YakShaver#3078 — same title (open) | exact-title dup of an open issue, merge |
+| SSWConsulting/SSW.YakShaver#3080 — 🐛 Bug - User Details Update Issue | SSWConsulting/SSW.YakShaver#3079 — same title (open) | exact-title dup of an open issue, merge |
+| SSWConsulting/SSW.YakShaver#3026 — ✨ UI Improvement - Add Hover State for Clickable Tools | SSWConsulting/SSW.YakShaver#3035 — same title (closed) | exact title; also matches #3032 closed — likely already done |
+| SSWConsulting/SSW.YakShaver#2661 — ✨ Add clickable links for email work items (dashboard) | SSWConsulting/SSW.YakShaver#2646 — same scope (open) | tokens identical; merge |
+| SSWConsulting/SSW.YakShaver#2623 — ✨ Exclude Non-YakShaver NuGet Packages from Code Coverage | SSWConsulting/SSW.YakShaver#2755 — same title (closed) | exact title — already shipped |
+| SSWConsulting/SSW.YakShaver#2609 — ✨ Implement Health Check UI for YakShaver | SSWConsulting/SSW.YakShaver#3185 — `zz🐛 Bug - YakShaver Health Check` (closed) | "zz" prefix typically marks deletion; verify if work was completed elsewhere |
+| SSWConsulting/SSW.YakShaver#2364 — ✨ Add Project or Email Option for Admin Tasks | SSWConsulting/SSW.YakShaver#2363 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#2281 — 📝 Doc - Update FAQ or Troubleshoot guide with BOT issues | SSWConsulting/SSW.YakShaver#2258 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#2220 — 🐛 Bug - Video Upload Fails for Large Files | SSWConsulting/SSW.YakShaver#2218 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#2219 — ✨ Enhance Task Cancellation Detection in YakShaver | SSWConsulting/SSW.YakShaver#2217 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#2136 — ✨ Rename Developer Account in Partner Center | SSWConsulting/SSW.YakShaver#2135 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#1847 — ✨ Website - Implement Carousel for Recent Articles on Mobile | SSWConsulting/SSW.YakShaver#1846 — "Add Carousel..." (closed) | same scope — already shipped as #1846 |
+| SSWConsulting/SSW.YakShaver#1583 — ✨#2 Cancelled video - add ❌ | SSWConsulting/SSW.YakShaver#1582 — ✨#1 same title (open) | "#1" and "#2" of the same idea — split or merge |
+| SSWConsulting/SSW.YakShaver#1355 — ✨ Collect Email Addresses for Interested Visitors | SSWConsulting/SSW.YakShaver#1364 — same scope (closed) | also matches closed #1363 — work shipped already |
+| SSWConsulting/SSW.YakShaver#1317 — 📊 Trello - Integrate Trello Task Creation | SSWConsulting/SSW.YakShaver#1524 — "Integrate YakShaver with Trello" (closed) | also #2740, #2706 closed — Trello has been opened/closed multiple times; verify current state |
+| SSWConsulting/SSW.YakShaver#1199 — ✨ Add ability to re-trigger failed or undetected videos | SSWConsulting/SSW.YakShaver#1201 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#1114 — ✨ Add Priority Specification for Work Items | SSWConsulting/SSW.YakShaver#1087 — same title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver#623 — ✨ Implement Naughty Words Filter for YakShaver | SSWConsulting/SSW.YakShaver#622 — same title (closed) | exact title duplicate; #623 is in-scope under epic #720 |
+| SSWConsulting/SSW.YakShaver#95 — Add config valiation [sic] | SSWConsulting/SSW.YakShaver#96 — same typo title (closed) | exact title duplicate |
+| SSWConsulting/SSW.YakShaver.Desktop#294 — Create dedicated GitHub App for MCP OAuth | SSWConsulting/SSW.YakShaver#3271 — same scope (closed) | cross-repo dup, work apparently shipped in #3271 |
+| SSWConsulting/SSW.YakShaver#3586 — 💄 Move "Delete Project" Button (references #3034) | SSWConsulting/SSW.YakShaver#3034 — same title (closed) | title explicitly references the closed #3034; verify and close |
+| SSWConsulting/SSW.YakShaver#3524 — Decouple Graph-dependent backend services | SSWConsulting/SSW.YakShaver#3534 — "Phase 2: same title" (closed) | same scope; #3534 marked Phase-2 and closed |
+| SSWConsulting/SSW.YakShaver#3523 — Model Entra/Graph capabilities explicitly | SSWConsulting/SSW.YakShaver#3533 — "Phase 1: same title" (closed) | same scope; #3533 marked Phase-1 and closed |
+| SSWConsulting/SSW.YakShaver#3111 — ✨ Handling Duplicate PBIs | SSWConsulting/SSW.YakShaver#3076 — Improve Handling of Duplicate PBIs (open) | overlapping scope — pick one |
+| SSWConsulting/SSW.YakShaver#2584 — ✨ Implement New Client Portal Design | SSWConsulting/SSW.YakShaver#1186 — Improve UI/UX Design (closed) | both reference the same portal redesign; verify if complete |
+| SSWConsulting/SSW.YakShaver.Desktop#794 — Update Desktop App to use new YakShaver logo | SSWConsulting/SSW.YakShaver#3118 — ✨ YakShaver Desktop App Logo (closed) | cross-repo dup — logo work may have been completed in #3118 |
+| SSWConsulting/SSW.YakShaver#3607 — ✨ YakShaver 360 - Frontend Infrastructure | SSWConsulting/SSW.YakShaver#3595 — 🚀 YakShaver 360 - Frontend (open Epic) | sub-issue title equals parent epic title — restructure |
+| SSWConsulting/SSW.YakShaver#3606 — ✨ YakShaver 360 - Backend Infrastructure | SSWConsulting/SSW.YakShaver#3596 — 🚀 YakShaver 360 - Backend (open Epic) | sub-issue title equals parent epic title — restructure |
+| SSWConsulting/SSW.YakShaver#3552 — ✨ Update toast notifications position to bottom center | SSWConsulting/SSW.YakShaver.Desktop#784 — same title (open) | cross-repo open↔open dup; pick one |
+| SSWConsulting/SSW.YakShaver#3086 — ✨ Switch to GPT 5 Mini for Chat Completion | SSWConsulting/SSW.YakShaver#3286 — Switch to GPT 5.2 Chat (closed) | model already updated past 5 Mini |
+| SSWConsulting/SSW.YakShaver#2391 — ✨ Update Mobile Menu Icon to Hamburger Style | SSWConsulting/SSW.YakShaver#2390 — Update Mobile Sidebar Icon to Hamburger Menu (closed) | same icon update — already shipped |
+| SSWConsulting/SSW.YakShaver360#57 — 🔍 Spike - Chinafy YakShaver-360 investigation | SSWConsulting/SSW.YakShaver360#23 — ✨ Chinafy YakShaver-360 (open) | spike vs implementation — spike likely complete or merged |
+| SSWConsulting/SSW.YakShaver#3256 — 🐛 Play button on MyShaves prompts sign-in despite Microsoft SSO | SSWConsulting/SSW.YakShaver#3255 — same bug, different wording (open) | same bug, two open tickets — merge |
+| SSWConsulting/SSW.YakShaver.Desktop#499 — 🐛 (PROMPT TEST) Open tag green instead of red | SSWConsulting/SSW.YakShaver.Desktop#497 — same bug (closed) | likely a PROMPT-TEST artifact dup |
+| SSWConsulting/SSW.YakShaver#2611 — 🐛 Error sending email + converting to work item | SSWConsulting/SSW.YakShaver#2171 — Error converting email to work item (closed) | also matches #1281, #2082 closed — verify if regression |
+| SSWConsulting/SSW.YakShaver#1672 — ✨ Separate Production Deployment for AI Models | SSWConsulting/SSW.YakShaver#249 — 👷 Production deployment (closed) | likely stale; verify if still relevant |
+| SSWConsulting/SSW.YakShaver#1253 — ✨ Teams App - Add 'projects' command | SSWConsulting/SSW.YakShaver#1260 — Add 'projects' command (closed) | exact-token dup |
+| SSWConsulting/SSW.YakShaver#562 — ✨ Integrate YakShaver with Jira | SSWConsulting/SSW.YakShaver#2178 — 🧠 Spike: Integrate Jira (closed) | spike done; #562 is the implementation — keep #562, dup is just historical |
+
+---
+
+## 3. Source epics suggested for close or archive
+
+| Epic | Reason |
+|------|--------|
+| SSWConsulting/SSW.YakShaver#498 — 🚀 People data sources other than Dynamics | 100% complete (1/1) |
+| SSWConsulting/SSW.YakShaver#717 — 🚀 Project data sources other than Dynamics | 100% complete (1/1) |
+| SSWConsulting/SSW.YakShaver#721 — 🚀 Transcript data source other than Teams or Snagit | 100% complete (6/6) |
+| SSWConsulting/SSW.YakShaver#2011 — 🚀 SSW Identity Server Integration | 100% complete (1/1); body is now superseded by #3494 Auth Migration |
+| SSWConsulting/SSW.YakShaver#2019 — 🚀 Stripe Integration + Release to Waitlist (MVP) | 100% complete (19/19); MVP shipped |
+| SSWConsulting/SSW.YakShaver#2097 — 🚀 Full Stripe Integration | 100% complete (2/2) |
+| SSWConsulting/SSW.YakShaver#3583 — ✨ 🏍️ Performance Optimization | Empty epic (0/0). Either populate concrete perf PBIs into proposed cluster 17 or close as placeholder |
+| SSWConsulting/SSW.YakShaver#3595 — 🚀 YakShaver 360 - Frontend | Thin (0/2 progress, 2 PBIs). Both PBIs (#3605, #3607) absorbed into proposed cluster 14 *YakShaver 360 — Initial Build*. Consider merging this epic into 360 |
+| SSWConsulting/SSW.YakShaver#3596 — 🚀 YakShaver 360 - Backend | Thin (0/2, same situation as #3595). #3604, #3606 absorbed into proposed cluster 14. Consider merging |
+
+---
+
+## 4. Epics skipped (already 100% complete)
+
+- SSWConsulting/SSW.YakShaver#498 — 🚀 People data sources other than Dynamics - Excel file, Active Directory, Google Cloud, etc. (1/1)
+- SSWConsulting/SSW.YakShaver#717 — 🚀 Project data sources other than Dynamics - Excel file, Salesforce, etc. (1/1)
+- SSWConsulting/SSW.YakShaver#721 — 🚀 Transcript data source other than Teams or Snagit - CustomGPT, Loom, OBS, etc. (6/6)
+- SSWConsulting/SSW.YakShaver#2011 — 🚀 SSW Identity Server Integration (1/1)
+- SSWConsulting/SSW.YakShaver#2019 — 🚀 Stripe Integration + Release to Waitlist (MVP) (19/19)
+- SSWConsulting/SSW.YakShaver#2097 — 🚀 Full Stripe Integration (2/2)
+
+---
+
+## 5. ✅ Reviewer checklist (verification only — do not act on GitHub yet)
+
+- [ ] Section 0 methodology counts look plausible (re-run a sample query manually if uncertain — try `gh issue list -R SSWConsulting/SSW.YakShaver --state all --limit 1 --json number` and compare to the largest issue number recorded)
+- [ ] Section 1 clusters look right — pay particular attention to the boundary between **Cluster 1 (Agent 2.0 core pipeline)**, **Cluster 2 (Desktop App distribution/UX)**, and **Cluster 3 (MCP plumbing)**, since #2811 sub-issues span all three
+- [ ] Section 2 duplicates are genuine — the open↔open pairs (3081/3078, 3080/3079, 3552/Desktop#784, 3349/Desktop#492, 3256/3255, 1582/1583, 2661/2646, 3111/3076, 360#57/360#23) are the highest-leverage to act on
+- [ ] Section 3 close candidates are correct — verify the 100%-complete epics have no recently re-opened sub-issues that flipped completion back below 100%; #3583 is intentionally empty (0/0)
+- [ ] Section 4 skipped epics are genuinely 100% complete
+
+> Once you've validated the audit across a few runs, run the analogous execute workflow (or future command) to act on this plan.