🛠️ Update various documentation, scripts, and configuration templates to enhance clarity, functionality, and maintainability across the project
This commit is contained in:
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
Reference in New Issue
Block a user