🛠️ Update various documentation, scripts, and configuration templates to enhance clarity, functionality, and maintainability across the project

This commit is contained in:
2026-05-04 10:56:41 +00:00
parent 1a2f1510bf
commit 31975e3088
41 changed files with 4184 additions and 133 deletions

View File

@@ -3,8 +3,8 @@
"SessionStart": [
{
"type": "command",
"osx": "~/.copilot-resources/resources/scripts/report-hook-event.sh",
"linux": "~/.copilot-resources/resources/scripts/report-hook-event.sh",
"osx": "bash ~/.copilot-resources/resources/scripts/report-hook-event.sh",
"linux": "bash ~/.copilot-resources/resources/scripts/report-hook-event.sh",
"windows": "powershell -NoProfile -File \"$HOME/.copilot-resources/resources/scripts/report-hook-event.ps1\""
}
]

View File

@@ -0,0 +1,15 @@
---
description: "Use when authoring or editing shared Copilot resources that should stay lightweight and inexpensive to run."
applyTo: "resources/prompts/**/*.prompt.md,resources/instructions/**/*.instructions.md,resources/agents/**/*.agent.md,resources/skills/**/SKILL.md"
---
- Reuse an existing shared resource before creating a new one.
- Prefer the cheapest sufficient primitive: instruction for durable rules, skill
for portable workflows, prompt for thin VS Code entrypoints, hook only for
deterministic enforced behavior.
- Keep frontmatter, examples, and repeated policy text short.
- Ask for narrow inputs such as a file, symbol, or command instead of broad
workspace scans when possible.
- Bias the resource toward concise outputs unless the task clearly needs depth.
- Say when the resource should not be used if that prevents broad, expensive
misuse.

View File

@@ -1,5 +1,38 @@
# MCP Notes
This folder is reserved for reusable MCP references and safe shared
configuration snippets. Machine-specific secrets and authenticated local server
definitions should stay out of the repository.
This repository manages shared MCP configuration through tracked templates plus
machine-local overrides.
## What Is Tracked
- Shared templates live in `config/mcp/`.
- The merge logic lives in `install/merge-managed-mcp-config.mjs`.
- The Copilot CLI filesystem wrapper lives in `install/mcp/`.
## What Stays Local
- Machine-local values live in `.local/mcp.local.jsonc`.
- Secrets stay in that local file and are never committed.
- Bootstrap creates `.local/mcp.local.jsonc` from
`config/mcp/local-overrides.example.jsonc` if it does not exist yet.
## Generated Outputs
- VS Code user MCP config: user-profile `mcp.json`
- Copilot CLI user MCP config: `~/.copilot/mcp-config.json`
Bootstrap and update regenerate those managed files while preserving unmanaged
entries already present in the user config.
## Managed Servers
- Playwright: generated for VS Code with `npx @playwright/mcp@latest`
- Filesystem: generated for VS Code with Docker and `${workspaceFolder}` binding
- Filesystem: generated for Copilot CLI with a repo-owned Node wrapper that
binds the current working directory into Docker
- Gitea/Forgejo: generated for VS Code and Copilot CLI with
`ronmi/forgejo-mcp`, but only when `.local/mcp.local.jsonc` enables it and
provides `serverUrl` plus `token`
Copilot CLI Playwright is not generated here because Copilot CLI already ships a
built-in Playwright MCP server.

View File

@@ -0,0 +1,17 @@
---
name: "audit-copilot-usage"
description: "Run the local Copilot reuse audit and review candidates for promotion into the shared resource repo."
agent: "agent"
tools: [read, search, execute]
argument-hint: "days=<default 30> workspace=<optional path filter> sources=<optional csv>"
---
Run the repository audit workflow by using the shared audit script.
Requirements:
- Prefer `resources/scripts/audit-copilot-usage.sh` over ad hoc searching.
- Keep the audit itself read-only.
- Review `audit-summary.md`, `candidates-report.tsv`, `selection-manifest.tsv`, and `pattern-details/`.
- Recommend promotion only for portable resources; keep machine-specific or
repo-specific patterns in audit notes.

View File

@@ -0,0 +1,16 @@
---
name: "prepare-audit-promotions"
description: "Generate draft resources or staging notes from approved audit manifest rows."
agent: "agent"
tools: [read, search, execute]
argument-hint: "audit=<optional audit directory>"
---
Generate promotion drafts from the audit approval surface by using the shared repository script.
Requirements:
- Prefer `resources/scripts/prepare-audit-promotions.sh` over manual translation.
- Treat `promote-*`, `template-only`, and `docs-only` rows as approved when their `decision` column is non-empty.
- Treat `discard`, `needs-sanitization`, and empty decisions as non-approved.
- Review `promotion-summary.md` after the script runs and summarize the generated draft resources and staging notes.

View File

@@ -0,0 +1,20 @@
---
name: "review-audit-candidates"
description: "Review an audit shortlist one candidate at a time and update the selection manifest with decisions and notes."
agent: "agent"
tools: [read, search, edit]
argument-hint: "audit=<optional audit directory>"
---
Review the audit shortlist one candidate at a time.
Requirements:
- Resolve the target audit directory from `audit=<path>` when provided, otherwise use the latest run under `.local/audits/`.
- Read `selection-manifest.tsv` first, then open each pending candidate's `detail_file` before asking for a decision.
- Summarize the candidate's purpose, expected benefit, audit context, and why it scored as it did before asking for a decision.
- Mention transcript prompt-cost proxy fields when they are present, but label them as cost signals rather than exact token counts.
- If the detail bundle has weak or missing evidence, say that explicitly and explain what the user would be deciding on anyway.
- Use `vscode_askQuestions` to present decision choices as buttons and allow freeform text for `review_note`.
- Update `selection-manifest.tsv` after each answer so review progress survives interruptions.
- Stop only when every row has a non-empty `decision` or the user tells you to stop.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
repo_root="$(cd -- "$script_dir/../.." && pwd -P)"
if command -v node >/dev/null 2>&1; then
node_bin="$(command -v node)"
elif command -v nodejs >/dev/null 2>&1; then
node_bin="$(command -v nodejs)"
else
printf 'Node.js is required to run the Copilot audit workflow.\n' >&2
exit 1
fi
exec "$node_bin" "$script_dir/audit-copilot-usage.mjs" --repo-root "$repo_root" "$@"

View File

@@ -0,0 +1,481 @@
#!/usr/bin/env node
// @ts-nocheck
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const APPROVED_DECISIONS = new Set([
'promote-skill',
'promote-instruction',
'promote-agent',
'promote-hook',
'promote-script',
'promote-prompt',
'template-only',
'docs-only',
]);
const DRAFTABLE_DECISIONS = new Set([
'promote-skill',
'promote-instruction',
'promote-agent',
'promote-prompt',
]);
function usage() {
console.error(
[
'Usage: prepare-audit-promotions.mjs [options]',
'',
'Options:',
' --audit-dir <path> Use a specific audit directory.',
' --machine-id <id> Limit latest-run discovery to one machine id.',
' --repo-root <path> Override the repository root.',
' --help Show this help text.',
].join('\n'),
);
}
function parseArgs(argv) {
const options = {
auditDir: '',
machineId: '',
repoRoot: path.resolve(__dirname, '../..'),
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === '--help' || arg === '-h') {
usage();
process.exit(0);
}
if (arg === '--audit-dir') {
options.auditDir = argv[index + 1] ?? '';
index += 1;
continue;
}
if (arg === '--machine-id') {
options.machineId = argv[index + 1] ?? '';
index += 1;
continue;
}
if (arg === '--repo-root') {
options.repoRoot = path.resolve(argv[index + 1] ?? options.repoRoot);
index += 1;
continue;
}
throw new Error(`Unknown argument: ${arg}`);
}
return options;
}
function ensureDir(dirPath) {
fs.mkdirSync(dirPath, { recursive: true });
}
function sanitizeMachineId(input) {
return String(input || os.hostname() || 'unknown-machine')
.trim()
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, '-')
.replace(/^-+|-+$/g, '') || 'unknown-machine';
}
function safeStat(filePath) {
try {
return fs.statSync(filePath);
} catch {
return null;
}
}
function listDirectories(rootPath) {
if (!fs.existsSync(rootPath)) {
return [];
}
return fs.readdirSync(rootPath, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(rootPath, entry.name));
}
function findLatestAuditDir(repoRoot, machineId) {
const auditRoot = path.join(repoRoot, '.local', 'audits');
const preferredMachineId = sanitizeMachineId(machineId);
const candidateMachineDirs = [];
if (machineId) {
const machineDir = path.join(auditRoot, preferredMachineId);
if (fs.existsSync(machineDir)) {
candidateMachineDirs.push(machineDir);
}
} else {
const localMachineDir = path.join(auditRoot, sanitizeMachineId(os.hostname()));
if (fs.existsSync(localMachineDir)) {
candidateMachineDirs.push(localMachineDir);
}
for (const machineDir of listDirectories(auditRoot)) {
if (!candidateMachineDirs.includes(machineDir)) {
candidateMachineDirs.push(machineDir);
}
}
}
let latestRun = null;
for (const machineDir of candidateMachineDirs) {
for (const runDir of listDirectories(machineDir)) {
const stat = safeStat(runDir);
if (!stat) {
continue;
}
if (!latestRun || stat.mtimeMs > latestRun.mtimeMs) {
latestRun = { path: runDir, mtimeMs: stat.mtimeMs };
}
}
}
return latestRun?.path ?? null;
}
function parseTsv(filePath) {
const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/).filter(Boolean);
if (lines.length === 0) {
return [];
}
const header = lines[0].split('\t');
return lines.slice(1).map((line) => {
const values = line.split('\t');
const row = {};
for (let index = 0; index < header.length; index += 1) {
row[header[index]] = values[index] ?? '';
}
return row;
});
}
function slugify(value) {
return String(value || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, '-');
}
function yamlString(value) {
return JSON.stringify(String(value ?? ''));
}
function preferredStem(row) {
return slugify(row.title || row.id || 'draft-resource') || 'draft-resource';
}
function relativeAuditPath(auditDir, filePath) {
return path.relative(auditDir, filePath) || '.';
}
function buildSourceBlock(row, detailRelativePath) {
const lines = [
`- Candidate ID: ${row.id}`,
`- Decision: ${row.decision}`,
`- Suggested action: ${row.suggested_action}`,
`- Category: ${row.category}`,
`- Value rank: ${row.value_rank}`,
];
if (detailRelativePath) {
lines.push(`- Detail file: ${detailRelativePath}`);
}
if (row.review_note) {
lines.push(`- Review note: ${row.review_note}`);
}
if (row.notes) {
lines.push(`- Audit notes: ${row.notes}`);
}
return lines.join('\n');
}
function renderSkillDraft(row, detailRelativePath) {
const name = preferredStem(row);
return [
'---',
`name: ${yamlString(name)}`,
`description: ${yamlString(`Draft generated from audit candidate ${row.id}.`)}`,
'argument-hint: ""',
'---',
'',
`# ${row.title}`,
'',
'Use this draft to turn the audited pattern into a portable shared skill.',
'',
'## Source',
'',
buildSourceBlock(row, detailRelativePath),
'',
'## Draft Workflow',
'',
'Replace this placeholder with the reusable workflow distilled from the audit evidence bundle.',
'',
'## Validation',
'',
'- Confirm the pattern is portable across repositories.',
'- Add any required docs, prompts, or scripts before publishing.',
'',
].join('\n');
}
function renderInstructionDraft(row, detailRelativePath) {
return [
'---',
`description: ${yamlString(`Draft generated from audit candidate ${row.id}.`)}`,
'applyTo: ""',
'---',
'',
'# Draft Instruction',
'',
'- Replace this placeholder with portable instruction text distilled from the audit evidence.',
'',
'## Source',
'',
buildSourceBlock(row, detailRelativePath),
'',
'## Guardrails',
'',
'- Remove repo-specific assumptions before publishing.',
'- Keep the instruction concise and reusable.',
'',
].join('\n');
}
function renderAgentDraft(row, detailRelativePath) {
return [
'---',
`name: ${yamlString(row.title)}`,
`description: ${yamlString(`Draft generated from audit candidate ${row.id}.`)}`,
'tools: [read, search, edit, execute]',
'---',
'',
`# ${row.title}`,
'',
'Use this draft agent to package the audited pattern into a reusable interaction mode.',
'',
'## Source',
'',
buildSourceBlock(row, detailRelativePath),
'',
'## Behavior',
'',
'- Replace this placeholder with the agent workflow distilled from the audit evidence.',
'- Specify when to use the agent and which tradeoffs it should enforce.',
'',
].join('\n');
}
function renderPromptDraft(row, detailRelativePath) {
const name = preferredStem(row);
return [
'---',
`name: ${yamlString(name)}`,
`description: ${yamlString(`Draft generated from audit candidate ${row.id}.`)}`,
'agent: "agent"',
'tools: [read, search, edit, execute]',
'argument-hint: ""',
'---',
'',
'Translate the audited pattern into a reusable prompt adapter.',
'',
'## Source',
'',
buildSourceBlock(row, detailRelativePath),
'',
'Requirements:',
'',
'- Replace this placeholder guidance with the portable workflow.',
'- Recheck the evidence bundle before publishing.',
'- Remove any repo-specific assumptions.',
'',
].join('\n');
}
function suggestedTarget(decision, stem) {
if (decision === 'promote-script') {
return `resources/scripts/${stem}.sh and resources/scripts/${stem}.ps1`;
}
if (decision === 'promote-hook') {
return `resources/hooks/${stem}.json`;
}
if (decision === 'template-only') {
return `templates/<target>/${stem}`;
}
if (decision === 'docs-only') {
return `docs/${stem}.md`;
}
return 'manual follow-up';
}
function renderStagingNote(row, detailRelativePath) {
const stem = preferredStem(row);
return [
`# ${row.title}`,
'',
'This approved audit row still needs manual design work before it should be published into the shared repository.',
'',
'## Source',
'',
buildSourceBlock(row, detailRelativePath),
'',
'## Suggested Target',
'',
`- ${suggestedTarget(row.decision, stem)}`,
'',
'## Next Steps',
'',
'- Distill the evidence bundle into a portable implementation plan.',
'- Decide whether this should stay a note, become docs, or become a concrete shared artifact.',
'',
].join('\n');
}
function buildDraftSpec(auditDir, row) {
const stem = preferredStem(row);
const detailRelativePath = row.detail_file || '';
if (row.decision === 'promote-skill') {
return {
kind: 'draft',
outputPath: path.join(auditDir, 'draft-resources', 'resources', 'skills', stem, 'SKILL.md'),
content: renderSkillDraft(row, detailRelativePath),
};
}
if (row.decision === 'promote-instruction') {
return {
kind: 'draft',
outputPath: path.join(auditDir, 'draft-resources', 'resources', 'instructions', `${stem}.instructions.md`),
content: renderInstructionDraft(row, detailRelativePath),
};
}
if (row.decision === 'promote-agent') {
return {
kind: 'draft',
outputPath: path.join(auditDir, 'draft-resources', 'resources', 'agents', `${stem}.agent.md`),
content: renderAgentDraft(row, detailRelativePath),
};
}
if (row.decision === 'promote-prompt') {
return {
kind: 'draft',
outputPath: path.join(auditDir, 'draft-resources', 'resources', 'prompts', `${stem}.prompt.md`),
content: renderPromptDraft(row, detailRelativePath),
};
}
return {
kind: 'note',
outputPath: path.join(auditDir, 'staging-notes', `${stem}.md`),
content: renderStagingNote(row, detailRelativePath),
};
}
function writeArtifacts(auditDir, approvedRows) {
const generated = [];
for (const row of approvedRows) {
const artifact = buildDraftSpec(auditDir, row);
ensureDir(path.dirname(artifact.outputPath));
fs.writeFileSync(artifact.outputPath, artifact.content, 'utf8');
generated.push({
decision: row.decision,
id: row.id,
kind: artifact.kind,
outputPath: artifact.outputPath,
});
}
return generated;
}
function writeSummary(auditDir, manifestPath, approvedRows, generatedArtifacts) {
const summaryPath = path.join(auditDir, 'promotion-summary.md');
const draftArtifacts = generatedArtifacts.filter((artifact) => artifact.kind === 'draft');
const noteArtifacts = generatedArtifacts.filter((artifact) => artifact.kind === 'note');
const lines = [
'# Audit Promotion Summary',
'',
`- Generated: ${new Date().toISOString()}`,
`- Audit directory: ${auditDir}`,
`- Selection manifest: ${manifestPath}`,
`- Approved rows: ${approvedRows.length}`,
`- Draft resources: ${draftArtifacts.length}`,
`- Staging notes: ${noteArtifacts.length}`,
'',
'## Draft Resources',
'',
];
if (draftArtifacts.length === 0) {
lines.push('- None');
} else {
for (const artifact of draftArtifacts) {
lines.push(`- ${artifact.decision} :: ${artifact.id} :: ${relativeAuditPath(auditDir, artifact.outputPath)}`);
}
}
lines.push('', '## Staging Notes', '');
if (noteArtifacts.length === 0) {
lines.push('- None');
} else {
for (const artifact of noteArtifacts) {
lines.push(`- ${artifact.decision} :: ${artifact.id} :: ${relativeAuditPath(auditDir, artifact.outputPath)}`);
}
}
fs.writeFileSync(summaryPath, `${lines.join('\n')}\n`, 'utf8');
return summaryPath;
}
function main() {
const options = parseArgs(process.argv.slice(2));
const auditDir = options.auditDir
? path.resolve(options.auditDir)
: findLatestAuditDir(options.repoRoot, options.machineId);
if (!auditDir) {
throw new Error('No audit directory was found. Run the audit first or pass --audit-dir.');
}
const manifestPath = path.join(auditDir, 'selection-manifest.tsv');
if (!fs.existsSync(manifestPath)) {
throw new Error(`Selection manifest not found: ${manifestPath}`);
}
const rows = parseTsv(manifestPath);
const approvedRows = rows.filter((row) => APPROVED_DECISIONS.has(row.decision));
const generatedArtifacts = writeArtifacts(auditDir, approvedRows);
const summaryPath = writeSummary(auditDir, manifestPath, approvedRows, generatedArtifacts);
console.log('Audit promotion preparation complete.');
console.log(`Audit directory: ${auditDir}`);
console.log(`Approved rows: ${approvedRows.length}`);
console.log(`Draft resources: ${generatedArtifacts.filter((artifact) => artifact.kind === 'draft').length}`);
console.log(`Staging notes: ${generatedArtifacts.filter((artifact) => artifact.kind === 'note').length}`);
console.log(`Summary: ${summaryPath}`);
}
main();

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
repo_root="$(cd -- "$script_dir/../.." && pwd -P)"
if command -v node >/dev/null 2>&1; then
node_bin="$(command -v node)"
elif command -v nodejs >/dev/null 2>&1; then
node_bin="$(command -v nodejs)"
else
printf 'Node.js is required to prepare audit promotion drafts.\n' >&2
exit 1
fi
exec "$node_bin" "$script_dir/prepare-audit-promotions.mjs" --repo-root "$repo_root" "$@"

0
resources/scripts/report-hook-event.sh Normal file → Executable file
View File

View File

@@ -0,0 +1,40 @@
---
name: copilot-cost-review
description: "Use when reviewing a prompt, instruction, agent, or skill draft for avoidable token cost and reuse opportunities."
argument-hint: "target=<resource path or description>"
---
# Copilot Cost Review
Use this skill when you want to make a Copilot resource cheaper to run without
stripping away the behavior it actually needs.
## Procedure
1. Identify the resource's real job, expected inputs, and default output size.
2. Search for an existing shared resource that already covers most of the same
workflow before proposing a new artifact.
3. Re-evaluate the primitive choice: use an instruction for durable rules, a
skill for a portable workflow, a prompt for a thin VS Code entrypoint, and a
hook only for deterministic enforced behavior.
4. Remove repeated policy text, long examples, and broad workspace-reading
requirements unless they materially improve correctness.
5. Replace broad discovery steps with narrow anchors such as a file, symbol,
command, or manifest row whenever possible.
6. Add or tighten an explicit output budget and note when the resource should
not be used.
7. If the resource is still too heavy, split reference material into docs or
scripts and keep the runtime resource concise.
## Outputs
- Recommended primitive
- Reuse candidates in the shared repo
- Context and prompt reductions
- Output-budget guidance
- Non-goals or usage limits
## Notes
- Reduce prompt size without removing information that changes correctness.
- Treat transcript prompt-cost fields as proxies, not exact billing data.

View File

@@ -0,0 +1,52 @@
---
name: copilot-reuse-audit
description: "Use when auditing the last 30 days of persisted Copilot artifacts for reusable patterns worth promoting into the shared repo."
argument-hint: "days=<n> workspace=<optional path filter> sources=<optional csv>"
---
# Copilot Reuse Audit
Use this skill when you want a repeatable audit of local Copilot usage artifacts
to find patterns that should become shared resources.
## Procedure
1. Run `resources/scripts/audit-copilot-usage.sh` instead of manually hunting
through transcripts, memories, and publish logs.
2. By default, the runner excludes the `copilot-resources` repo root from
workspace-backed candidate discovery so the audit does not ask you to review
patterns that already live in the shared repository.
3. Review the generated `audit-summary.md` first to understand coverage and top
candidates.
4. Use `selection-manifest.tsv` and `pattern-details/*.md` as the approval
surface for each candidate.
5. When you want interactive triage inside Copilot, use the
`review-audit-candidates` prompt so each pending row is handled one at a
time and written back to `selection-manifest.tsv`.
6. Use the transcript prompt-cost proxy fields to prioritize repeated long
prompts that are likely worth turning into a shared resource.
7. Treat those prompt-cost fields as triage signals, not as exact billing data.
6. Before asking for a decision, explain the candidate's likely purpose,
concrete benefit, audit context, and score rationale. If evidence is thin,
call that out explicitly instead of asking the user to infer it.
8. Promote only candidates that map cleanly to a portable skill, instruction,
prompt adapter, agent, hook, script, or template.
9. After rows are approved, run `resources/scripts/prepare-audit-promotions.sh`
to create draft resources or staging notes in the audit directory.
10. Keep non-portable or repo-specific findings in audit notes rather than
forcing them into shared resources.
## Outputs
- `audit-summary.md` — high-level run summary and top candidates
- `candidates-report.tsv` — scored candidate list with source references
- `selection-manifest.tsv` — editable approval surface for promotion decisions
- `pattern-details/*.md` — per-candidate evidence bundles with benefit, context, score rationale, and caveats
- `draft-resources/` — generated draft resource files for approved portable rows
- `staging-notes/` — generated follow-up notes for approved rows that still need design work
## Notes
- The runner is macOS-first in this iteration.
- Audit history is stored per machine under `.local/audits/<machine-id>/` in the
repo checkout and is intentionally git-ignored.

View File

@@ -5,3 +5,9 @@
- Confirm frontmatter is valid and descriptive.
- Avoid duplicating a workflow that already exists.
- If scripts are referenced, validate them before merging.
- Check whether the same outcome can be achieved with a cheaper shared
primitive or by reusing an existing resource.
- Keep examples and embedded guidance short unless the extra context is
essential.
- Say when the resource should not be used if that prevents wasteful generic
usage.

View File

@@ -5,4 +5,8 @@
- Add clear frontmatter.
- Check naming and portability rules.
- Validate any referenced scripts.
- Reuse an existing shared resource when possible instead of adding a near
duplicate.
- Keep descriptions and examples compact.
- Note the default output budget or brevity expectation when it matters.
- Commit and push after publishing.