diff --git a/.github/workflows/apply-changeset.yml b/.github/workflows/apply-changeset.yml new file mode 100644 index 0000000..a088a6c --- /dev/null +++ b/.github/workflows/apply-changeset.yml @@ -0,0 +1,106 @@ +name: Knowledge Distillery — Apply Changeset + +on: + pull_request: + types: [closed] + branches: [main, master] + +jobs: + apply-changeset: + if: >- + github.event.pull_request.merged && + startsWith(github.event.pull_request.head.ref, 'knowledge/batch-') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.base.ref }} + fetch-depth: 0 + + - name: Checkout Knowledge Distillery plugin + uses: actions/checkout@v6 + with: + repository: ether-moon/knowledge-distillery + ref: main + path: .knowledge-distillery-plugin + + - name: Configure git identity + run: | + git config user.name "knowledge-distillery[bot]" + git config user.email "knowledge-distillery[bot]@users.noreply.github.com" + + - name: Extract batch date from branch name + id: batch + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + run: | + if [[ ! "$BRANCH" =~ ^knowledge/batch-([0-9]{4}-[0-9]{2}-[0-9]{2})(-[0-9]+)?$ ]]; then + echo "::error::Unexpected branch format: ${BRANCH}" + exit 1 + fi + DATE="${BASH_REMATCH[1]}" + echo "date=${DATE}" >> "$GITHUB_OUTPUT" + echo "Batch date: ${DATE}" + + - name: Find and apply changeset + run: | + GATE=$(find .knowledge-distillery-plugin -name knowledge-gate -path '*/scripts/*' -type f | head -1) + if [ -z "$GATE" ]; then + echo "::error::knowledge-gate script not found in plugin checkout" + exit 1 + fi + chmod +x "$GATE" + + CHANGESET=".knowledge/changesets/batch-${{ steps.batch.outputs.date }}.json" + + if [ ! -f "$CHANGESET" ]; then + echo "::warning::No changeset file found at ${CHANGESET} — skipping" + exit 0 + fi + + ACCEPTED=$(jq '[.entries[] | select(.status == "accepted")] | length' "$CHANGESET") + echo "Accepted entries to apply: ${ACCEPTED}" + + if [ "$ACCEPTED" -eq 0 ]; then + echo "No accepted entries — skipping vault update" + exit 0 + fi + + "$GATE" _changeset-apply "$CHANGESET" + + - name: Commit and push vault.db + run: | + if git diff --quiet .knowledge/vault.db 2>/dev/null; then + echo "No vault.db changes — skipping commit" + exit 0 + fi + + git add .knowledge/vault.db + git commit -m "knowledge: apply batch ${{ steps.batch.outputs.date }} changeset" + git push origin ${{ github.event.pull_request.base.ref }} + + - name: Clean up batch artifacts + env: + BATCH_DATE: ${{ steps.batch.outputs.date }} + run: | + CHANGESET=".knowledge/changesets/batch-${BATCH_DATE}.json" + REPORT=".knowledge/reports/batch-${BATCH_DATE}.md" + CHANGED=false + + if [ -f "$CHANGESET" ]; then + git rm "$CHANGESET" + CHANGED=true + fi + + if [ -f "$REPORT" ]; then + git rm "$REPORT" + CHANGED=true + fi + + if [ "$CHANGED" = true ]; then + git pull --rebase origin ${{ github.event.pull_request.base.ref }} + git commit -m "knowledge: clean up batch ${BATCH_DATE} artifacts" + git push origin ${{ github.event.pull_request.base.ref }} + fi diff --git a/.github/workflows/batch-refine.yml b/.github/workflows/batch-refine.yml new file mode 100644 index 0000000..7c792d3 --- /dev/null +++ b/.github/workflows/batch-refine.yml @@ -0,0 +1,98 @@ +name: Knowledge Distillery — Batch Refine + +on: + schedule: + - cron: '0 0 * * 1' # Every Monday 09:00 KST (00:00 UTC) + workflow_dispatch: + +jobs: + collect-and-refine: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Checkout Knowledge Distillery plugin + uses: actions/checkout@v6 + with: + repository: ether-moon/knowledge-distillery + ref: main + path: .knowledge-distillery-plugin + + - name: Write dynamic MCP config + run: | + if [ -n "${{ secrets.LINEAR_API_KEY }}" ]; then echo "::add-mask::${{ secrets.LINEAR_API_KEY }}"; fi + if [ -n "${{ secrets.SLACK_API_KEY }}" ]; then echo "::add-mask::${{ secrets.SLACK_API_KEY }}"; fi + if [ -n "${{ secrets.NOTION_API_KEY }}" ]; then echo "::add-mask::${{ secrets.NOTION_API_KEY }}"; fi + cat > .mcp.json << 'MCPEOF' + { + "mcpServers": { + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "Bearer ${{ secrets.GITHUB_TOKEN }}", + "X-MCP-Toolsets": "pull_requests,issues,labels" + } + }, + "linear": { + "type": "stdio", + "command": "npx", + "args": ["-y", "mcp-linear"], + "env": { + "LINEAR_API_KEY": "${{ secrets.LINEAR_API_KEY }}" + } + }, + "slack": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-slack"], + "env": { + "SLACK_BOT_TOKEN": "${{ secrets.SLACK_API_KEY }}" + } + }, + "notion": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@notionhq/notion-mcp-server"], + "env": { + "OPENAPI_MCP_HEADERS": "{\"Authorization\": \"Bearer ${{ secrets.NOTION_API_KEY }}\", \"Notion-Version\": \"2022-06-28\"}" + } + } + } + } + MCPEOF + + - name: Configure git identity + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + - uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + Use skill /knowledge-distillery:batch-refine. + Find all PRs with 'knowledge:pending' label, + collect evidence using each PR's Evidence Bundle Manifest, run refinement pipeline, + write accepted entries to a changeset file. + Naming conventions (MUST follow exactly): + - Report branch: knowledge/batch-YYYY-MM-DD (e.g. knowledge/batch-2026-03-27) + - Changeset file: .knowledge/changesets/batch-YYYY-MM-DD.json + - Only entries with .entries[].status == "accepted" are included + On success: update label to 'knowledge:collected'. + On insufficient evidence: leave label as 'knowledge:pending' and report the reason. + Create a Report PR with change summary. + Do NOT modify vault.db directly — the changeset will be applied on merge. + claude_args: "--plugin-dir .knowledge-distillery-plugin --allowedTools 'mcp__github__*,mcp__linear__*,mcp__slack__*,mcp__notion__*,Bash(*),Read(*),Write(*),Glob(*),Grep(*),Skill(*),Agent(*)'" + show_full_output: true + + - name: Cleanup sensitive files + if: always() + run: rm -f .mcp.json diff --git a/.github/workflows/curate-report.yml b/.github/workflows/curate-report.yml new file mode 100644 index 0000000..2c3462b --- /dev/null +++ b/.github/workflows/curate-report.yml @@ -0,0 +1,94 @@ +name: Knowledge Distillery — Curate Report + +on: + issue_comment: + types: [created] + +jobs: + curate-report: + if: >- + github.event.issue.pull_request && + contains(github.event.comment.body, '/curate') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + steps: + - name: Get PR details + id: pr + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + const branch = pr.data.head.ref; + if (!branch.startsWith('knowledge/batch-')) { + core.notice('Skipping: not a Report PR branch (' + branch + ')'); + core.setOutput('skip', 'true'); + return; + } + core.setOutput('skip', 'false'); + core.setOutput('branch', branch); + core.setOutput('pr_number', context.issue.number); + + - uses: actions/checkout@v6 + if: steps.pr.outputs.skip != 'true' + with: + ref: ${{ steps.pr.outputs.branch }} + fetch-depth: 0 + + - name: Checkout Knowledge Distillery plugin + if: steps.pr.outputs.skip != 'true' + uses: actions/checkout@v6 + with: + repository: ether-moon/knowledge-distillery + ref: main + path: .knowledge-distillery-plugin + + - name: Write dynamic MCP config + if: steps.pr.outputs.skip != 'true' + run: | + cat > .mcp.json << 'MCPEOF' + { + "mcpServers": { + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "Bearer ${{ secrets.GITHUB_TOKEN }}", + "X-MCP-Toolsets": "pull_requests,issues" + } + } + } + } + MCPEOF + + - name: Configure git identity + if: steps.pr.outputs.skip != 'true' + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + - uses: anthropics/claude-code-action@v1 + if: steps.pr.outputs.skip != 'true' + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + Use skill /knowledge-distillery:curate-report. + Process reviewer feedback on Report PR #${{ steps.pr.outputs.pr_number }} + (branch: ${{ steps.pr.outputs.branch }}). + Read all PR comments, classify feedback into reject/update/keep actions, + update the changeset file (.knowledge/changesets/), regenerate the batch report, commit, and post summary. + Do NOT modify vault.db directly — operate on the changeset file only. + claude_args: "--plugin-dir .knowledge-distillery-plugin --allowedTools 'mcp__github__*,Bash(*),Read(*),Write(*),Glob(*),Grep(*),Skill(*),Agent(*)'" + show_full_output: true + + - name: Cleanup sensitive files + if: always() && steps.pr.outputs.skip != 'true' + run: rm -f .mcp.json diff --git a/.github/workflows/mark-evidence.yml b/.github/workflows/mark-evidence.yml new file mode 100644 index 0000000..9c2b3c0 --- /dev/null +++ b/.github/workflows/mark-evidence.yml @@ -0,0 +1,69 @@ +name: Knowledge Distillery — Mark Evidence + +on: + pull_request: + types: [closed] + branches: [main, master] + +jobs: + mark-evidence: + if: >- + github.event.pull_request.merged == true && + !startsWith(github.event.pull_request.head.ref, 'knowledge/batch-') + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Checkout Knowledge Distillery plugin + uses: actions/checkout@v6 + with: + repository: ether-moon/knowledge-distillery + ref: main + path: .knowledge-distillery-plugin + + - name: Write dynamic MCP config + run: | + if [ -n "${{ secrets.LINEAR_API_KEY }}" ]; then echo "::add-mask::${{ secrets.LINEAR_API_KEY }}"; fi + cat > .mcp.json << 'MCPEOF' + { + "mcpServers": { + "github": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "Bearer ${{ secrets.GITHUB_TOKEN }}", + "X-MCP-Toolsets": "pull_requests,issues,labels" + } + }, + "linear": { + "type": "stdio", + "command": "npx", + "args": ["-y", "mcp-linear"], + "env": { + "LINEAR_API_KEY": "${{ secrets.LINEAR_API_KEY }}" + } + } + } + } + MCPEOF + + - uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + Use skill /knowledge-distillery:mark-evidence for PR #${{ github.event.pull_request.number }}. + Extract evidence identifiers, write Evidence Bundle Manifest as PR comment, + and add 'knowledge:pending' label. + claude_args: "--plugin-dir .knowledge-distillery-plugin --allowedTools 'mcp__github__*,mcp__linear__*,Bash(*),Read(*),Glob(*),Grep(*),Skill(*),Agent(*)'" + show_full_output: true + + - name: Cleanup sensitive files + if: always() + run: rm -f .mcp.json diff --git a/.gitignore b/.gitignore index 19d6007..5b3acff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,13 @@ .DS_Store node_modules/ .claude/ + +# Knowledge Distillery — vault is committed as binary, reports are committed +# Only ignore temporary/working files +.knowledge/tmp/ + +# Vault usage tracking (consumed and cleared by memento-commit) +tmp/ + +# Dynamic MCP config — contains secrets at runtime, must never be committed +.mcp.json diff --git a/.knowledge/vault.db b/.knowledge/vault.db new file mode 100644 index 0000000..208a58c Binary files /dev/null and b/.knowledge/vault.db differ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3f0aac6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,16 @@ +## Knowledge Vault +- A UserPromptSubmit hook reminds you to query the vault when active entries exist +- When the hook fires and the task involves code modifications, query before planning: + - Single file: `knowledge-gate query-paths ` (summary index by default) + - Multiple files: `knowledge-gate domain-resolve-path ` → `knowledge-gate query-domain ` (summary index by default) + - Topic search: `knowledge-gate search ` (summary index by default) + - Fetch full details only for the specific entries you need: `knowledge-gate get ` or `knowledge-gate get-many ` +- MUST/MUST-NOT rules from returned entries must be strictly followed +- For structural changes in areas without related rules, confirm with a human first +- Do not directly read files in the .knowledge/ directory + +## Memento +- After every git commit, attach a memento session summary as a git note on `refs/notes/commits` +- The summary follows the 7-section format: Decisions Made, Problems Encountered, Constraints Identified, Open Questions, Context, Recorded Decisions, Vault Entries Referenced +- See `/knowledge-distillery:memento-commit` for the full workflow and format specification +- If the PostToolUse hook fires a reminder, follow it — generate the summary and attach the note