Skip to content
Closed
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
17 changes: 17 additions & 0 deletions .ai/mcp/mcp-servers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# MCP servers

<!--
Human-readable inventory of the MCP servers this repo uses and what each is for.
Never contains connection secrets — per-tool config files hold the wiring.
-->

## HCS Governance MCP

- **Purpose:** source of truth for standards, hard rules, scope resolution, orchestration guidance, and the auth broker.
- **Endpoint:** `https://mcp.hybridsolutions.cloud/mcp`
- **Transport:** Streamable HTTP (remote)
- **Auth:** OAuth 2.1 brokered to Microsoft Entra; members of `sg-hcs-mcp-users` only. Clients prompt for Entra sign-in on first connect.
- **Bootstrap:** call `bootstrap(repo="azurelocal-platform", client="<your client>")` at session start.
- **Per-tool config:** `.mcp.json` (Claude Code), `.codex/config.toml` (Codex), `.gemini/settings.json` (Gemini), `.cursor/mcp.json` (Cursor), `.vscode/mcp.json` (VS Code Copilot).

<!-- Add any additional MCP servers this repo uses below. -->
9 changes: 9 additions & 0 deletions .ai/memory/COMMANDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Commands

<!-- How to build, test, and run THIS specific repo. Exact commands, not generalities. -->

```powershell
# TODO: fill in
# TODO: fill in
# TODO: fill in
```
5 changes: 5 additions & 0 deletions .ai/memory/DECISIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Decisions

<!-- Architecture and design decisions WITH their reasoning, so a future session doesn't relitigate a settled choice. -->

_None recorded yet._

Check failure on line 5 in .ai/memory/DECISIONS.md

View workflow job for this annotation

GitHub Actions / Markdown lint

Files should end with a single newline character

.ai/memory/DECISIONS.md:5:20 MD047/single-trailing-newline Files should end with a single newline character https://github.com/DavidAnson/markdownlint/blob/v0.35.0/doc/md047.md
5 changes: 5 additions & 0 deletions .ai/memory/GOTCHAS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Gotchas

<!-- Non-obvious traps, footguns, and workarounds that would otherwise cost the next session time. -->

_None recorded yet._
5 changes: 5 additions & 0 deletions .ai/memory/PROJECT_CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Project context

<!-- Durable knowledge about what this project is and how it fits the wider estate. Rarely changes. -->

_Not yet filled in. See CLAUDE.md / AGENTS.md for the auto-generated repo description in the meantime._
12 changes: 12 additions & 0 deletions .ai/state/CURRENT_TASK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Current task

<!-- What is being worked on right now. Keep it short; update as work moves. -->

_Nothing yet - this repo was just onboarded onto the multi-model standard._

<!--
Optional advisory model hint the next tool should honour if available.
Never overrides an explicit per-session model flag the operator has set.
Example: suggested-model: opus
-->
<!-- suggested-model: -->
16 changes: 16 additions & 0 deletions .ai/state/HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Handoff

<!--
Written at the END of every session by whichever tool was used.
This is the single most important cross-tool file — the next session
(possibly a different tool) starts by reading it.
-->

## Last session

- **What changed and why:** Initial multi-model scaffold onboarding (AGENTS.md, CLAUDE.md shim, Codex/Copilot config, .mcp.json, .ai/ workspace).
- **Files touched:** AGENTS.md, CLAUDE.md, .codex/config.toml, .github/copilot-instructions.md, .mcp.json, .ai/*
- **Commands / tests run and results:** N/A - scaffold only
- **Branch:** main — committed: yes — pushed: no
- **Blockers:** None
- **Exact next steps:** Fill in .ai/memory/PROJECT_CONTEXT.md and .ai/memory/COMMANDS.md with real repo-specific detail.
5 changes: 5 additions & 0 deletions .ai/state/OPEN_QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Open questions

<!-- Unresolved questions or deferred decisions for the next session or tool to pick up. -->

_None yet._
33 changes: 33 additions & 0 deletions .claude/agents/azurelocal-platform-engineer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: azurelocal-platform-engineer
description: azurelocal-platform engineer — general development and maintenance
model: sonnet
tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---

You are the Engineer for azurelocal-platform — Repo in the HCS estate managed under the platform engineering standard.

## Repo structure

- See CLAUDE.md in this repo for the current directory layout.

## Stack / conventions

- Primary language: Mixed
- Commit format: `type(scope): short description`
- No credentials, tokens, or subscription IDs committed to any file.
- Local path: D:/git/azurelocal/platform

## What you do

You write and maintain code in this repo according to the type and conventions above. You run linters and validators appropriate to the stack. You create and update files, commit changes, and follow HCS platform standards.

## Hard rules

- No credentials, tokens, subscription IDs, or vault passwords committed to any file
33 changes: 33 additions & 0 deletions .claude/hooks/block-secrets.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# PreToolUse hook — blocks writes containing secret patterns

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the mandatory PowerShell prologue to every hook

This and all five other newly added .claude/hooks/*.ps1 scripts omit #Requires -Version 7.0, Set-StrictMode -Version Latest, and $ErrorActionPreference = 'Stop', despite the explicit hard rule in AGENTS.md:45. Besides violating the repository standard, missing strict/error behavior allows property and I/O failures in these security and audit hooks to continue or exit successfully rather than being surfaced.

Useful? React with 👍 / 👎.

# Event: PreToolUse Write|Edit
# Input: JSON from stdin { tool_name, tool_input: { file_path, content|new_string } }

$input_json = $input | Out-String
try { $payload = $input_json | ConvertFrom-Json } catch { exit 0 }

$content = if ($payload.tool_input.content) { $payload.tool_input.content }
elseif ($payload.tool_input.new_string) { $payload.tool_input.new_string }
else { exit 0 }

$patterns = @(
'api[_-]?key\s*[:=]\s*[''"]?[A-Za-z0-9+/]{20,}',
'password\s*[:=]\s*[''"]?[^\s''";]{8,}',
'secret\s*[:=]\s*[''"]?[A-Za-z0-9+/]{16,}',
'token\s*[:=]\s*[''"]?[A-Za-z0-9._-]{20,}',
'-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----',
'AKIA[0-9A-Z]{16}', # AWS access key
'ghp_[A-Za-z0-9]{36}', # GitHub personal token
'ghs_[A-Za-z0-9]{36}', # GitHub server token
'sk-[A-Za-z0-9]{48}', # OpenAI key
'DefaultEndpointsProtocol=https.*AccountKey=', # Azure Storage connection string
'Server=.*Password=' # DB connection string with password
)

foreach ($pattern in $patterns) {
if ($content -match $pattern) {
Write-Error "BLOCKED: Possible secret detected matching pattern: $pattern"
exit 1
}
}

exit 0
98 changes: 98 additions & 0 deletions .claude/hooks/check-context.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Context watcher for Claude Code (UserPromptSubmit hook).
#
# When the active session's input context exceeds THRESHOLD tokens, injects a
# system reminder telling Claude to write a memory dump and prompt the user
# to /clear. The user does the /clear themselves; this script never blocks
# the prompt.
#
# INSTALL: copy to ~/.claude/hooks/check-context.ps1
# Register in ~/.claude/settings.json under hooks.UserPromptSubmit (see
# settings.user-level.json.template in this directory).
#
# Failure mode: any error → exit 0 silently. The user's prompt is never
# delayed by a parsing or IO problem here.

$THRESHOLD = 90000

try {
$stdin = [Console]::In.ReadToEnd()
if ([string]::IsNullOrWhiteSpace($stdin)) { exit 0 }

$hookInput = $stdin | ConvertFrom-Json -ErrorAction Stop
$transcriptPath = $hookInput.transcript_path
if ([string]::IsNullOrWhiteSpace($transcriptPath) -or `
-not (Test-Path -LiteralPath $transcriptPath)) {
exit 0
}

$lines = [System.IO.File]::ReadAllLines($transcriptPath)
if ($lines.Count -eq 0) { exit 0 }

# Walk backwards to find the most recent assistant turn that recorded
# token usage. Total context = input_tokens + cache_creation + cache_read.
$tokens = 0
for ($i = $lines.Count - 1; $i -ge 0; $i--) {
$line = $lines[$i]
if ([string]::IsNullOrWhiteSpace($line)) { continue }

try {
$entry = $line | ConvertFrom-Json -ErrorAction Stop
} catch { continue }

$usage = $entry.message.usage
if (-not $usage) { $usage = $entry.usage }
if (-not $usage) { continue }
if (-not $usage.input_tokens) { continue }

$cacheCreate = if ($usage.cache_creation_input_tokens) {
[int]$usage.cache_creation_input_tokens
} else { 0 }
$cacheRead = if ($usage.cache_read_input_tokens) {
[int]$usage.cache_read_input_tokens
} else { 0 }
$tokens = [int]$usage.input_tokens + $cacheCreate + $cacheRead
break
}

# Fallback: char-count / 4 if no usage telemetry found (very early
# in a session, or if the schema changed).
if ($tokens -eq 0) {
$totalChars = 0
foreach ($line in $lines) { $totalChars += $line.Length }
$tokens = [int]($totalChars / 4)
}

if ($tokens -lt $THRESHOLD) { exit 0 }

$tokensK = [math]::Round($tokens / 1000)
$thresholdK = [math]::Round($THRESHOLD / 1000)

$msg = @"
[CONTEXT-WATCH] Session context: ~${tokensK}k tokens (threshold: ${thresholdK}k).

Before responding to the user's actual message, write a comprehensive memory dump covering everything from this session that should survive a /clear: in-flight work (branches, files, PR/issue numbers, mid-implementation state), decisions made and their reasoning, bugs found and fixes applied, blockers, open questions, verification status, and any user feedback or preferences observed.

Write to this project's auto-memory directory (the path is in the auto-memory section of your system prompt). Use existing categories (project / feedback / user / reference) and your judgement on granularity — broad is better than narrow. Update MEMORY.md.

End your turn with this exact line:
"Memory saved (N files updated). Run /clear when ready — your next session will auto-load."

The user has explicitly chosen to handle /clear themselves. Do NOT attempt to clear the session or run any harness command yourself.

If this reminder fires again on a subsequent turn (because /clear hasn't happened yet), append/update incrementally — don't rewrite from scratch.
"@

$output = @{
hookSpecificOutput = @{
hookEventName = "UserPromptSubmit"
additionalContext = $msg
}
} | ConvertTo-Json -Depth 4 -Compress

[Console]::Out.Write($output)
exit 0
}
catch {
# Fail open — never block the user.
exit 0
}
47 changes: 47 additions & 0 deletions .claude/hooks/format-on-write.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# PostToolUse hook — format-on-write
# Fires on Write|Edit. Extracts the file path and applies a formatter if one is configured.
#
# Current behavior:
# .ps1 — skip (no PS formatter configured)
# .md — skip (no markdown formatter configured)
# .ts/.js — runs prettier if available (uncomment below)
# .py — runs black if available (uncomment below)
#
# To add a formatter: uncomment the relevant block and adjust the command.

$input_json = $input | Out-String
try { $payload = $input_json | ConvertFrom-Json } catch { exit 0 }

$filePath = if ($payload.file_path) { $payload.file_path }
elseif ($payload.tool_input.file_path) { $payload.tool_input.file_path }
else { exit 0 }

if (-not $filePath) { exit 0 }

$ext = [System.IO.Path]::GetExtension($filePath).TrimStart('.').ToLower()

switch ($ext) {
'ps1' {
# PowerShell: skip
}
'md' {
# Markdown: skip (no formatter configured)
}
{ $_ -in @('ts', 'tsx', 'js', 'jsx') } {
# TypeScript/JavaScript: uncomment to enable prettier
# if (Get-Command prettier -ErrorAction SilentlyContinue) {
# prettier --write $filePath 2>$null
# }
}
'py' {
# Python: uncomment to enable black
# if (Get-Command black -ErrorAction SilentlyContinue) {
# black $filePath 2>$null
# }
}
default {
# Plug in your formatter here for other file types.
}
}

exit 0
21 changes: 21 additions & 0 deletions .claude/hooks/log-tokens.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# PostToolUse hook — logs tool usage to .claude/logs/tokens.jsonl
# Event: PostToolUse Write|Edit
# Input: JSON from stdin { tool_name, tool_input, tool_response }
# Configure: set $logDir to your repo's .claude/logs/ path

$logDir = "D:\git\azurelocal\platform\.claude\logs"

$input_json = $input | Out-String
try { $payload = $input_json | ConvertFrom-Json } catch { exit 0 }

if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }

$entry = @{
timestamp = (Get-Date -Format 'o')
tool_name = $payload.tool_name
file_path = $payload.tool_input.file_path
session_id = $env:CLAUDE_SESSION_ID
} | ConvertTo-Json -Compress

Add-Content -Path "$logDir\tokens.jsonl" -Value $entry -Encoding UTF8
Comment on lines +6 to +20
exit 0
16 changes: 16 additions & 0 deletions .claude/hooks/summarize-session.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Stop hook — logs session end event to .claude/logs/sessions.jsonl
# Event: Stop
# Configure: set $logDir to your repo's .claude/logs/ path

$logDir = "D:\git\azurelocal\platform\.claude\logs"

if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }

$entry = @{
timestamp = (Get-Date -Format 'o')
event = "session_stop"
session_id = $env:CLAUDE_SESSION_ID
} | ConvertTo-Json -Compress

Add-Content -Path "$logDir\sessions.jsonl" -Value $entry -Encoding UTF8
Comment on lines +5 to +15
exit 0
31 changes: 31 additions & 0 deletions .claude/hooks/validate-path.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# PreToolUse hook — blocks writes to protected paths
# Event: PreToolUse Write|Edit
# Input: JSON from stdin { tool_name, tool_input: { file_path } }

$input_json = $input | Out-String
try { $payload = $input_json | ConvertFrom-Json } catch { exit 0 }

$filePath = $payload.tool_input.file_path
if (-not $filePath) { exit 0 }

$normalizedPath = $filePath.Replace('\', '/').ToLower()

# Add or remove paths as needed for your repo
$blocked = @(
'/.git/',
'/node_modules/',
'/dist/',
'/.env',
'/dist-ssr/',
'/.next/',
'/build/'
)

foreach ($b in $blocked) {
if ($normalizedPath -like "*$b*") {
Write-Error "BLOCKED: Write to protected path: $filePath (matched: $b)"
exit 1
}
}

exit 0
Loading
Loading