🛠️ Update various documentation, scripts, and configuration templates to enhance clarity, functionality, and maintainability across the project
This commit is contained in:
@@ -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\""
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
17
resources/prompts/audit-copilot-usage.prompt.md
Normal file
17
resources/prompts/audit-copilot-usage.prompt.md
Normal 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.
|
||||
16
resources/prompts/prepare-audit-promotions.prompt.md
Normal file
16
resources/prompts/prepare-audit-promotions.prompt.md
Normal 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.
|
||||
20
resources/prompts/review-audit-candidates.prompt.md
Normal file
20
resources/prompts/review-audit-candidates.prompt.md
Normal 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.
|
||||
1486
resources/scripts/audit-copilot-usage.mjs
Normal file
1486
resources/scripts/audit-copilot-usage.mjs
Normal file
File diff suppressed because it is too large
Load Diff
17
resources/scripts/audit-copilot-usage.sh
Executable file
17
resources/scripts/audit-copilot-usage.sh
Executable 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" "$@"
|
||||
481
resources/scripts/prepare-audit-promotions.mjs
Normal file
481
resources/scripts/prepare-audit-promotions.mjs
Normal 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();
|
||||
17
resources/scripts/prepare-audit-promotions.sh
Executable file
17
resources/scripts/prepare-audit-promotions.sh
Executable 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
0
resources/scripts/report-hook-event.sh
Normal file → Executable file
40
resources/skills/copilot-cost-review/SKILL.md
Normal file
40
resources/skills/copilot-cost-review/SKILL.md
Normal 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.
|
||||
52
resources/skills/copilot-reuse-audit/SKILL.md
Normal file
52
resources/skills/copilot-reuse-audit/SKILL.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user