Skip to content
Open
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/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Select all that apply:
* [ ] Copilot prompt (`.github/prompts/*.prompt.md`)
* [ ] Copilot agent (`.github/agents/*.agent.md`)
* [ ] Copilot skill (`.github/skills/*/SKILL.md`)
* [ ] Copilot hook (`.github/hooks/*/*.json`)
* [ ] Eval spec added/updated for changed AI artifacts (`evals/`)

> Note for AI Artifact Contributors:
Expand Down
2 changes: 2 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The project is organized into these main areas:
* Documentation (`docs/`) - Getting started guides, templates, RPI workflow documentation, and contribution guidelines.
* Scripts (`scripts/`) - Automation for linting, security validation, extension packaging, and development tools.
* Skills (`.github/skills/{collection-id}/`) - Self-contained skill packages, by convention organized by collection.
* Hooks (`.github/hooks/{collection-id}/`) - Collection-scoped Copilot hook manifests (JSON) that wire lifecycle event commands.
* Extension (`extension/`) - VS Code extension source and packaging.
* GitHub Configuration (`.github/`) - Workflows, instructions, prompts, agents, composite actions, and issue templates, typically organized into `{collection-id}` subdirectories.
* Collections (`collections/`) - YAML and markdown manifests defining bundled sets of agents, prompts, instructions, and skills.
Expand Down Expand Up @@ -152,6 +153,7 @@ Commit message scopes map to repository directories:
* `(prompts)` = `.github/prompts/`
* `(instructions)` = `.github/instructions/`
* `(skills)` = `.github/skills/`
* `(hooks)` = `.github/hooks/`
* `(templates)` = `.github/ISSUE_TEMPLATE/`
* `(workflows)` = `.github/workflows/`
* `(extension)` = `extension/`
Expand Down
70 changes: 70 additions & 0 deletions .github/hooks/shared/telemetry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"version": 1,
"description": "Records Copilot session lifecycle events to local telemetry for reporting.",
"hooks": {
"sessionStart": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"userPromptSubmit": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"preToolUse": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"postToolUse": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"subagentStart": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"subagentStop": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"stop": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
],
"preCompact": [
{
"type": "command",
"bash": ".github/hooks/shared/telemetry/telemetry-collector.sh",
"powershell": ".github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1",
"timeoutSec": 10
}
]
}
}
87 changes: 87 additions & 0 deletions .github/hooks/shared/telemetry/Invoke-TelemetryClean.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
#Requires -Version 7.0

<#
.SYNOPSIS
Removes telemetry artifacts written by the Copilot telemetry hooks.
.DESCRIPTION
Delegates to the shared Python engine's clean mode. By default cleans this
project's telemetry store; -AllDirs extends the cleanup to every registered
project plus the user-level HVE home directory. This thin wrapper keeps the
cleanup logic in a single implementation (Python) shared with the bash entry
point clean-telemetry.sh.
.PARAMETER AllDirs
Also clean every per-project telemetry directory recorded in the user-level
registry, plus the generated launchers, report, and registry in the HVE home
directory.
.PARAMETER Path
Telemetry directory. Default: <repo>/.copilot-tracking/telemetry
.PARAMETER DryRun
List what would be removed without deleting anything.
.PARAMETER Force
Skip the confirmation prompt (required for non-interactive use).
.NOTES
Runs via: pwsh Invoke-TelemetryClean.ps1
#>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param(
[switch]$AllDirs,
[string]$Path,
[switch]$DryRun,
[switch]$Force
)

$ErrorActionPreference = 'Stop'

#region Resolve repo root
$RepoRoot = $env:HVE_REPO_ROOT
if (-not $RepoRoot -and (Get-Command git -ErrorAction SilentlyContinue)) {
try { $RepoRoot = & git -C $PSScriptRoot rev-parse --show-toplevel 2>$null } catch { $RepoRoot = $null }
}
if (-not $RepoRoot) {
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../..')).Path
}
#endregion Resolve repo root

# Require Python3 for the shared telemetry engine
$Python = Get-Command python3 -ErrorAction SilentlyContinue
if (-not $Python) {
$Python = Get-Command python -ErrorAction SilentlyContinue
}
if (-not $Python) {
Write-Error "'python3' is required but not installed"
exit 1
}

# Resolve the shared telemetry engine from the skill directory
$CorePy = Join-Path $PSScriptRoot '_telemetry_core.py'
if (-not (Test-Path $CorePy)) {
Write-Error "Telemetry engine not found: $CorePy"
exit 1
}

$TelemetryDir = if ($Path) { $Path } else { Join-Path $RepoRoot '.copilot-tracking/telemetry' }

# Prompt before destructive deletion. Skipped on -DryRun (non-destructive) and
# bypassed with -Force (required for non-interactive use).
if (-not $DryRun -and -not $Force) {
$scope = if ($AllDirs) {
'ALL registered telemetry stores plus the user-level HVE home directory'
} else {
$TelemetryDir
}
if (-not $PSCmdlet.ShouldProcess($scope, 'Permanently remove telemetry artifacts')) {
Write-Host 'Aborted.'
exit 0
}
}

# Build the clean-mode argument list mirroring the bash wrapper's flags.
$CliArgs = @('clean')
if ($AllDirs) { $CliArgs += '--all-dirs' }
if ($DryRun) { $CliArgs += '--dry-run' }

$env:HVE_TELEMETRY_DIR = $TelemetryDir
& $Python.Source $CorePy @CliArgs
98 changes: 98 additions & 0 deletions .github/hooks/shared/telemetry/Invoke-TelemetryCollector.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env pwsh
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: MIT
#Requires -Version 7.0

<#
.SYNOPSIS
Copilot hook handler that delegates telemetry collection to the shared Python engine.
.DESCRIPTION
Reads JSON from stdin for each hook lifecycle event, checks the opt-in gate,
and delegates all processing to _telemetry_core.py. This thin wrapper keeps
the collection logic in a single implementation (Python) shared with the bash
hook entry point.
.NOTES
Runs via: Copilot agent hook (stdin JSON, stdout JSON)
#>
[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'

#region Resolve repo root
$RepoRoot = $env:HVE_REPO_ROOT
if (-not $RepoRoot -and (Get-Command git -ErrorAction SilentlyContinue)) {
try { $RepoRoot = & git rev-parse --show-toplevel 2>$null } catch { $RepoRoot = $null }
}
if (-not $RepoRoot) {
$RepoRoot = '.'
}
#endregion Resolve repo root

#region Opt-in gate
$Enabled = $env:HVE_TELEMETRY -eq '1'
if (-not $Enabled) {
$MarkerPath = Join-Path $RepoRoot '.hve-telemetry'
$Enabled = Test-Path $MarkerPath
}
if (-not $Enabled) {
'{"continue":true}'
return
}
#endregion Opt-in gate

# Require Python3 for JSON processing
$Python = Get-Command python3 -ErrorAction SilentlyContinue
if (-not $Python) {
$Python = Get-Command python -ErrorAction SilentlyContinue
}
if (-not $Python) {
Write-Warning 'HVE telemetry enabled but python3 not found — events will not be recorded'
'{"continue":true}'
return
}

# Resolve the shared telemetry engine from the skill directory
$CorePy = Join-Path $PSScriptRoot '_telemetry_core.py'

if (-not (Test-Path $CorePy)) {
Write-Warning "Telemetry engine not found at $CorePy — events will not be recorded"
'{"continue":true}'
return
}

$TelemetryDir = if ($env:HVE_TELEMETRY_DIR) { $env:HVE_TELEMETRY_DIR } else { Join-Path $RepoRoot '.copilot-tracking/telemetry' }
if (-not (Test-Path $TelemetryDir)) {
New-Item -ItemType Directory -Path $TelemetryDir -Force | Out-Null
}

# Delegate all JSON processing to the shared Python telemetry engine
$RawInput = $input | Out-String

# Dump raw input for diagnostics (first 5 events only). This records hook
# payloads verbatim, including the full prompt text and tool inputs such as
# file contents and shell command strings, which can contain secrets. The
# processed sessions-*.jsonl stream already provides the diagnostic signal,
# so the verbatim dump is a separate explicit opt-in (off by default) layered
# on top of the telemetry gate. See docs/customization/local-telemetry.md.
if ($env:HVE_TELEMETRY_RAW -eq '1') {
$RawLog = Join-Path $TelemetryDir 'raw-input.jsonl'
$RawCount = 0
if (Test-Path $RawLog) {
$RawCount = (Get-Content -LiteralPath $RawLog).Count
}
if ($RawCount -lt 5) {
Add-Content -LiteralPath $RawLog -Value $RawInput
}
}

try {
$env:HVE_REPO_ROOT = $RepoRoot
$env:HVE_TELEMETRY_DIR = $TelemetryDir
$RawInput | & $Python.Source $CorePy collect 2>$null
}
catch {
Write-Verbose "Telemetry collection error: $_"
}

'{"continue":true}'
Loading
Loading