Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ For complex investigation tasks, use these skills (read the skill file for detai
| **test-planner** | `.github/skills/test-planner/SKILL.md` | "create test plan", "write test cases", "add tests to ADO", "export test plan", "E2E tests for" |
| **threat-modeler** | `.github/skills/threat-modeler/SKILL.md` | "create a threat model", "threat model for", "threat model diagram", "STRIDE analysis for", "security diagram for" |
| **copilot-review-analyst** | `.github/skills/copilot-review-analyst/SKILL.md` | "analyze Copilot reviews", "Copilot review effectiveness", "review analysis report", "how helpful are Copilot reviews" |
| **skill-evolver** | `.github/skills/skill-evolver/SKILL.md` | "improve/evolve/fix my skills", "run a skill retrospective", "what went wrong with X skill", "why didn't skill Y trigger", "this skill is outdated/wrong", "review skill friction", "you keep making the same mistake", "that didn't go well" |

## 13. Azure DevOps Integration

Expand Down
142 changes: 142 additions & 0 deletions .github/hooks/friction-capture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env node
/**
* friction-capture.js — OPTIONAL automatic friction capture for skill-evolver.
*
* ┌─────────────────────────────────────────────────────────────────────────┐
* │ DORMANT ON GITHUB COPILOT CLI. │
* │ This is a Claude Code-style lifecycle hook (PostToolUse / Stop). The │
* │ GitHub Copilot CLI runtime has no hooks system, so it NEVER fires here │
* │ and is intentionally NOT registered in orchestrator.json. │
* │ │
* │ On Copilot CLI, ACTIVE capture is the real mechanism: the agent records │
* │ friction itself via `journal-utils.js record` (see the skill-evolver │
* │ SKILL.md). Do not rely on this file to catch anything on Copilot CLI. │
* │ │
* │ Kept for teams that run this repo under Claude Code: register it in │
* │ `.claude/settings.json` (PostToolUse + Stop) and it will work there. │
* └─────────────────────────────────────────────────────────────────────────┘
*
* Behavior when it DOES fire (Claude Code): reads the hook payload from stdin and
* - PostToolUse: if the tool reported a failure/error, appends a high-signal
* `tool_error` friction event to the journal (attributed to the active skill).
* - Stop / SubagentStop: clears the active-skill marker so attribution does not
* leak across tasks.
*
* Design rules:
* - Never block the tool flow. Always print {continue:true} and exit 0.
* - Wrap everything in try/catch; capture is best-effort.
* - Only record on detected failure to keep the journal high-signal.
*/

'use strict';

var fs = require('fs');
var path = require('path');

function emitAndExit() {
console.log(JSON.stringify({ continue: true }));
process.exit(0);
}

// Read stdin (hook input)
var hookInput = {};
try {
hookInput = JSON.parse(fs.readFileSync(0, 'utf-8'));
} catch (e) {
// no stdin / not JSON — nothing to capture
emitAndExit();
}

// Global off switch — set SKILL_EVOLUTION_DISABLE=1 to silence all capture.
if (process.env.SKILL_EVOLUTION_DISABLE) {
emitAndExit();
}

// Avoid re-entry loops
if (hookInput.stop_hook_active) {
emitAndExit();
}

var journal;
try {
journal = require('./journal-utils.js');
} catch (e) {
// store unavailable — never block the tool flow
emitAndExit();
}

var eventName = hookInput.hook_event_name || hookInput.hookEventName || '';

try {
// End-of-task events: clear attribution so the next task starts clean.
if (eventName === 'Stop' || eventName === 'SubagentStop') {
journal.clearActive();
emitAndExit();
}

// From here we treat the payload as a (Post)ToolUse event.
var toolName = hookInput.tool_name || hookInput.toolName || 'unknown-tool';
var resp = hookInput.tool_response || hookInput.toolResponse || hookInput.result || {};

var failure = detectFailure(resp);
if (failure.failed) {
journal.recordEvent({
tool: toolName,
eventType: 'tool_error',
severity: failure.severity,
expected: 'Tool call to complete successfully',
actual: failure.summary,
detail: failure.detail,
source: 'hook',
sessionId: hookInput.session_id || hookInput.sessionId || null
});
}
} catch (e) {
// swallow — capture must never break the session
}

emitAndExit();

/**
* Heuristically decide whether a tool response represents a failure, and how bad.
* Conservative on purpose: false positives create journal noise.
*/
function detectFailure(resp) {
var result = { failed: false, severity: 'medium', summary: '', detail: '' };
if (resp === null || resp === undefined) return result;

// Explicit structured failure signals
if (resp.success === false || resp.is_error === true || resp.isError === true || resp.error) {
result.failed = true;
}

// Non-zero exit codes (powershell / shell-style tools)
var exitCode = resp.exit_code !== undefined ? resp.exit_code
: (resp.exitCode !== undefined ? resp.exitCode : undefined);
if (typeof exitCode === 'number' && exitCode !== 0) {
result.failed = true;
result.severity = 'high';
}

// String/text payloads that smell like errors
var text = '';
if (typeof resp === 'string') text = resp;
else text = [resp.error, resp.stderr, resp.message, resp.output, resp.content]
.filter(function (x) { return typeof x === 'string'; }).join('\n');

if (!result.failed && text) {
if (/\b(error|exception|failed|fatal|cannot find|not found|denied|traceback)\b/i.test(text)) {
result.failed = true;
}
}

if (result.failed) {
var src = (typeof resp.error === 'string' && resp.error) ||
(typeof resp.stderr === 'string' && resp.stderr) ||
(typeof resp.message === 'string' && resp.message) || text || 'Tool reported a failure';
result.detail = String(src);
result.summary = result.detail.split('\n')[0].slice(0, 200);
if (/\b(fatal|denied|traceback|exception)\b/i.test(result.detail)) result.severity = 'high';
}
return result;
}
Loading
Loading