Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/custom-layer-autoclose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Hey @{{AUTHOR}} 👋

First off — thank you for jumping in and experimenting! It's awesome to see people pushing on the framework. 🎉

That said, let me gently redirect you, because I think there's a small but important misunderstanding about how the `custom` layer is meant to work:

The `custom` layer in *this* repo isn't a destination for PRs — it's the designated sandbox inside **your own fork**. Think of it as the "your timeline" branch of the multiverse 🌌: this repo is canon, your fork is where you get to remix the lore without needing anyone's approval. That's the whole point of the layer existing — so you *don't* have to upstream your team-specific or experimental work.

The intended workflow is:

1. 🍴 **Fork** BCQuality to your own GitHub account
2. Clone *your fork* locally
3. Drop your custom agents and knowledge into the `custom` layer **there**
4. Commit and push to your fork — no PR back to upstream needed for custom stuff

That way you get full control, your changes survive upstream updates cleanly, and you can pull in new core releases from this repo whenever you want. ✨

**Now — here's the fun part:** if while building out your fork you discover knowledge, patterns, or agents that you think would genuinely benefit *everyone* using BCQuality (not just your team), that's exactly what the `/community` layer is for! 🌟 PRs to `/community` here in the upstream repo are absolutely welcome and encouraged — it's how the collective hive mind 🧠 levels up. So please: tinker in your fork, and when you strike gold that's worth sharing, send it our way via `/community`.

Going to close this PR for now (since it's targeting `custom` rather than `/community`), but please don't read it as a "no" — it's a "yes, but let's route it correctly." 🙏 Happy to help if you hit any snags spinning up your fork, and genuinely looking forward to seeing what you contribute to `/community` down the line.

<details>
<summary>Files in this PR that triggered the auto-close</summary>

{{FILES}}
</details>

May your merges be conflict-free. 🚀

---
<sub>🤖 This PR was closed automatically by the `Guard custom layer` workflow because it adds or changes content under `/custom/`. If you were only updating the template (`custom/README.md` or a `.gitkeep`), a maintainer can re-open it. If you think this was closed in error, just comment here.</sub>
10 changes: 10 additions & 0 deletions .github/new-top-level-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- guard:new-top-level -->
👋 Heads up @{{AUTHOR}} — and cc maintainers — this PR introduces **new top-level entries** that aren't part of BCQuality's known repository structure:

{{ENTRIES}}

This isn't a block — just a flag. 🚩 New top-level folders and files are *usually* unintended (a stray export, a tool's scratch dir, or content that meant to land inside an existing layer like `/community/knowledge/`). BCQuality keeps a deliberately small root: `.github/`, `community/`, `custom/`, `microsoft/`, `skills/`, and `tools/`, plus a handful of root docs.

**If this was intentional** and the new entry genuinely belongs at the repo root, a maintainer can review and merge as normal — no action needed beyond a quick sanity check. **If it wasn't**, please move the content into the right existing layer (or drop it) and push an update. 🙏

A maintainer will take a look before merging.
108 changes: 108 additions & 0 deletions .github/workflows/flag-new-top-level.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Flag new top-level entries

# BCQuality keeps a deliberately small repository root. New top-level folders
# or files are almost always unintended — a stray export, a tool's scratch
# directory, or content that meant to land inside an existing layer (e.g.
# /community/knowledge/). PR #55 leaked exactly this kind of stray folder.
#
# Unlike the custom-layer guard, this workflow does NOT close the PR. It only
# posts a single advisory comment so a maintainer (and the author) can eyeball
# the addition. It reads the PR's file LIST via the API and never checks out or
# runs PR code.

on:
pull_request_target:
types: [opened, reopened, synchronize]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
flag:
if: github.repository == 'microsoft/BCQuality'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
sparse-checkout: |
.github/new-top-level-flag.md
sparse-checkout-cone-mode: false

- name: Flag unexpected new top-level entries
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');

// Known, intended repository root. Anything else added at the root
// is flagged for a human to eyeball.
const ALLOWED_DIRS = new Set([
'.github', 'community', 'custom', 'microsoft', 'skills', 'tools',
]);
const ALLOWED_FILES = new Set([
'.gitignore', 'CODEOWNERS', 'LICENSE', 'README.md',
'SECURITY.md', 'agent-consumption.md',
]);

const MARKER = '<!-- guard:new-top-level -->';
const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;

const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: prNumber, per_page: 100,
});

// Only consider newly-added paths — a new top-level entry can only
// appear via an added file.
const added = files
.filter((f) => f.status === 'added')
.map((f) => f.filename);

const newDirs = new Set();
const newFiles = new Set();
for (const p of added) {
const slash = p.indexOf('/');
if (slash === -1) {
// Top-level file.
if (!ALLOWED_FILES.has(p)) newFiles.add(p);
} else {
// Top-level directory.
const dir = p.slice(0, slash);
if (!ALLOWED_DIRS.has(dir)) newDirs.add(dir);
}
}

if (newDirs.size === 0 && newFiles.size === 0) {
core.info('No unexpected new top-level entries. Nothing to flag.');
return;
}

// Idempotency: don't re-flag on every synchronize.
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});
if (comments.some((c) => c.body && c.body.includes(MARKER))) {
core.info('Already flagged on this PR. Skipping duplicate comment.');
return;
}

const lines = [];
for (const d of [...newDirs].sort()) lines.push(`- 📁 \`${d}/\` (new top-level folder)`);
for (const f of [...newFiles].sort()) lines.push(`- 📄 \`${f}\` (new top-level file)`);
const entries = lines.join('\n');

core.warning(`Unexpected new top-level entries: ${[...newDirs, ...newFiles].join(', ')}`);

let body = fs.readFileSync('.github/new-top-level-flag.md', 'utf8');
body = body
.replace(/{{AUTHOR}}/g, context.payload.pull_request.user.login)
.replace(/{{ENTRIES}}/g, entries);

await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body,
});

core.info(`Flagged PR #${prNumber}.`);
88 changes: 88 additions & 0 deletions .github/workflows/guard-custom-layer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Guard custom layer

# The /custom/ layer is a template: in upstream microsoft/BCQuality it stays
# empty by default (README.md + .gitkeep placeholders only). Custom knowledge
# and skills are partner/customer-specific and belong in a fork, never upstream.
#
# This workflow auto-closes any PR that adds or changes content under /custom/
# (anything beyond the allowed template files). It runs only on the upstream
# repo, so forks that legitimately populate /custom/ are unaffected.
#
# pull_request_target is required so the workflow runs with a token that can
# comment on and close the PR (including PRs opened from forks). It only reads
# the PR's file LIST via the API and never checks out or executes PR code, so
# the elevated token is not exposed to untrusted content.

on:
pull_request_target:
types: [opened, reopened, synchronize]

permissions:
contents: read
pull-requests: write
issues: write

jobs:
guard:
# Never run on forks — a fork's /custom/ content is exactly what's supposed
# to live there.
if: github.repository == 'microsoft/BCQuality'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
sparse-checkout: |
.github/custom-layer-autoclose.md
sparse-checkout-cone-mode: false

- name: Close PR if it touches the custom layer
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');

// Files under custom/ that ARE allowed to change (the template seed).
const ALLOWED = new Set([
'custom/README.md',
]);
// Any .gitkeep under custom/ is also allowed.
const isAllowed = (p) =>
ALLOWED.has(p) || /^custom\/.*\.gitkeep$/.test(p) || p === 'custom/.gitkeep';

const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;

const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: prNumber, per_page: 100,
});

// Offending = added/modified/renamed/copied/changed paths under custom/
// that are not template files. (We ignore pure deletions.)
const offending = files
.filter((f) => f.status !== 'removed')
.map((f) => f.filename)
.filter((p) => p.startsWith('custom/') && !isAllowed(p));

if (offending.length === 0) {
core.info('No disallowed /custom/ changes found. Nothing to do.');
return;
}

core.warning(`PR #${prNumber} touches the custom layer: ${offending.join(', ')}`);

const fileList = offending.map((p) => `- \`${p}\``).join('\n');
let body = fs.readFileSync('.github/custom-layer-autoclose.md', 'utf8');
body = body
.replace(/{{AUTHOR}}/g, context.payload.pull_request.user.login)
.replace(/{{FILES}}/g, fileList);

await github.rest.issues.createComment({
owner, repo, issue_number: prNumber, body,
});

await github.rest.pulls.update({
owner, repo, pull_number: prNumber, state: 'closed',
});

core.info(`Closed PR #${prNumber}.`);
11 changes: 11 additions & 0 deletions skills/write.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ Knowledge files do not contain code. Samples live as **sibling files** next to t
- **`/community/knowledge/<domain>/`** — shared community patterns. The default layer for contributions from outside the platform team. Content here can be promoted to `/microsoft/` once it proves itself.
- **`/custom/knowledge/<domain>/`** — partner or customer overrides. Generally does not appear in the BCQuality repository itself; `/custom/` lives in consumer repositories.

### Writing to `/custom/` — fork precondition

The `/custom/` layer is **empty by default** in the upstream `microsoft/BCQuality` repository — it ships as a template (`README.md` plus `.gitkeep` placeholders) and is meant to be populated only inside a **fork or consumer clone** that an organization controls. Custom content is partner- or customer-specific by definition and is never accepted upstream.

Before authoring or scaffolding any file under `/custom/knowledge/` or `/custom/skills/`, an author — human or agent — MUST confirm the working repository is **not** `microsoft/BCQuality`:

- Check the `origin` remote: `git remote get-url origin`. If it points at `github.com/microsoft/BCQuality`, stop — you are in the upstream repo, not a fork.
- If you are in the upstream repo, do not write the file. Either fork the repository (or clone it into your organization's own repo) and add the custom content there, or — if the guidance is genuinely shareable — author it in `/community/knowledge/` instead.

A pull request that adds `/custom/` content to `microsoft/BCQuality` will be **automatically closed** by the `Guard custom layer` workflow. Validate the fork precondition first so authoring effort is not wasted on a PR that cannot be merged.

## Pre-PR checklist

Before opening a pull request:
Expand Down
Loading