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
79 changes: 79 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project: Chronos for Azure

Chronos for Azure is a lightweight Azure Functions engine that starts and stops Azure Virtual Machines based on CRON schedules supplied via Azure Resource Tags. Users tag VMs with start and/or stop expressions; Chronos discovers those tags, schedules the corresponding lifecycle events, and executes them — no per-VM configuration or code changes required. The goal is zero-friction, tag-driven cost optimization.

The repository was originally written on .NET 6 (C# in-process Durable Functions, Bicep) and is undergoing a full rewrite. Assume nothing from the legacy code — patterns, tag names, and architecture are being redesigned.

## Target stack (authoritative)

- **Runtime**: .NET 10, Azure Functions isolated worker
- **Hosting**: Flex Consumption plan (Linux) — required for .NET 10 + Durable on Linux, scales to zero
- **Orchestration**: Azure Durable Functions — **Durable Entity per VM + eternal per-VM orchestrator + activities** (decided; see `README.md` → Architecture)
- **Durable backend**: Azure Storage (default provider) for v1; Durable Task Scheduler is a future migration if scale demands it
- **Discovery**: Azure Resource Graph (single KQL query, multi-subscription-ready)
- **IaC**: Terraform (AzureRM provider), under `infra/`
- **Deployment**: Azure Developer CLI (`azd`) headless, orchestrating the Terraform deployment. An `azure.yaml` will be added in a later feature
- **Source layout**: `src/` for Functions code, `infra/` for Terraform, `deploy/` currently held empty via `.gitkeep` pending the infra decision

None of the above code exists yet. Project initialization establishes conventions and architecture; implementation lands in subsequent features.

## Architecture (decided)

Four components — full description, diagram, and rationale live in [`README.md`](../README.md) (`## Architecture`). Summary:

- **`DiscoveryTimer`** (5-min Timer trigger) — ARG query for tagged VMs → signals each `VmScheduleEntity` with the materialized schedule, or `Clear()` for vanished tags. Spawns the orchestrator for actionable entities that don't yet have one.
- **`VmScheduleEntity`** — Durable Entity keyed by ARM resource ID. Holds start CRON, stop CRON, timezone, exclusion flag, last action. Source of truth for per-VM intent.
- **`VmScheduler`** — eternal Durable Orchestrator, one per VM. Reads entity, arms a durable timer to the next CRON occurrence, races it against the `ScheduleChanged` external event. On timer: calls start/stop activity. On event: recomputes. Always `ContinueAsNew`.
- **Start / Stop activities** — idempotent wrappers over `Azure.ResourceManager.Compute` via user-assigned managed identity.

**Tag mutation mechanism (every case): discovery signals entity → entity raises `ScheduleChanged` → orchestrator reacts.** Edits update the schedule and re-arm the timer; full removal makes the orchestrator exit cleanly. No special cases.

The standalone Durable Task SDK was evaluated and rejected — see the README section. Do not propose it again without a concrete reason.

## Scheduling scenarios the engine must support

The exact tag names are **not yet defined**. Refer to these scenarios by behavior:

1. **Stop-only**: VM has a stop/deallocate schedule but no start schedule → Chronos stops it on schedule and never starts it.
2. **Start-only**: VM has a start schedule but no stop schedule → Chronos starts it on schedule.
3. **Start + stop**: VM has both schedules → Chronos runs both.
4. **Exclusion**: VM has an exclusion tag → Chronos ignores the VM entirely, even if start/stop tags are present. Users must not be forced to remove schedule tags to opt out.
5. **Timezone override**: VM has a timezone tag → Chronos parses it and interprets the CRON expressions in that timezone. Invalid/unparseable timezones must be handled gracefully.

Do **not** invent concrete tag names. When implementing a feature that needs them, ask the user first.

## Workflow & conventions

### Branching
- `main` is protected — **never** commit or push to it directly.
- Every change (code, infra, docs) happens on a feature branch and lands via pull request.

### Commit message prefixes
- `feat:` — new feature
- `bug:` — hotfix
- `docs:` — documentation
- `refactor:` — refactoring of existing functionality

### Pull request title prefixes
- `Feature: …`
- `Documentation: …`
- `Refactoring: …`

(No dedicated PR prefix for hotfixes — use `Feature:` or ask the user.)

### Commit approval

**Always ask before creating a commit.** Before running `git commit`, present the proposed commit message to the user and wait for explicit confirmation. Never commit unilaterally, even when changes are obviously ready. This applies to every commit — the user's approval of a prior commit does not roll forward to the next.

Do **not** add Claude attribution, co-author trailers, or "Generated with Claude Code" footers to commit messages or PR bodies. The harness is configured (`.claude/settings.json` → `attribution`) to suppress these; don't reintroduce them manually.

## Working-style notes for Claude

- **Use current Microsoft documentation.** Patterns for Azure Functions have shifted substantially since .NET 6 (isolated worker model, new Durable Functions APIs, DI conventions). Do not replicate legacy in-process patterns. When in doubt, fetch current Microsoft Learn docs for Azure Functions, Durable Functions, .NET 10, and the Terraform AzureRM provider.
- **Architecture is fixed; do not relitigate.** The decisions in "Target stack" and "Architecture (decided)" — Durable Entities + eternal per-VM orchestrator on Flex Consumption with Azure Storage backend — were made deliberately and are documented in `README.md`. Do not propose alternatives (pure Timer trigger, ad-hoc orchestrations per event, standalone Durable Task SDK, external state store) without a concrete new reason.
- **Open questions remain — ask before deciding these.** Concrete Chronos tag *names*, the manual-override surface (HTTP API shape), and Event Grid integration timing are all undecided. When implementing a feature that touches these, ask first.
- **No speculative scaffolding.** Don't generate full Function apps or Terraform modules without an explicit go-ahead for that feature.
54 changes: 54 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"attribution": {
"commit": "",
"pr": ""
},
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git status *)",
"Bash(git log)",
"Bash(git log *)",
"Bash(git diff)",
"Bash(git diff *)",
"Bash(git show)",
"Bash(git show *)",
"Bash(git branch)",
"Bash(git branch *)",
"Bash(git remote -v)",
"Bash(git remote show *)",
"Bash(git blame *)",
"Bash(git reflog)",
"Bash(git reflog *)",
"Bash(git rev-parse *)",
"Bash(git describe *)",

"Bash(git add *)",
"Bash(git commit *)",
"Bash(git push)",
"Bash(git push *)",
"Bash(git checkout *)",
"Bash(git switch *)",
"Bash(git restore *)",
"Bash(git stash)",
"Bash(git stash *)",
"Bash(git pull)",
"Bash(git pull *)",
"Bash(git fetch)",
"Bash(git fetch *)",
"Bash(git merge *)",
"Bash(git rebase *)",

"Bash(gh issue list*)",
"Bash(gh issue view*)",
"Bash(gh issue status*)",
"Bash(gh pr list*)",
"Bash(gh pr view*)",
"Bash(gh pr status*)",
"Bash(gh pr diff*)",
"Bash(gh pr checks*)",
"Bash(gh repo view*)"
]
}
}
29 changes: 0 additions & 29 deletions .github/qodana.yaml

This file was deleted.

Empty file added .github/workflows/.gitkeep
Empty file.
82 changes: 0 additions & 82 deletions .github/workflows/codeql.yml

This file was deleted.

71 changes: 0 additions & 71 deletions .github/workflows/dotnet-desktop.yml

This file was deleted.

19 changes: 0 additions & 19 deletions .github/workflows/qodana_code_quality.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# Claude Code personal overrides
.claude/settings.local.json

# User-specific files
*.rsuser
*.suo
Expand Down
12 changes: 6 additions & 6 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "Attach to .NET Functions",
"type": "coreclr",
"request": "attach",
"processId": "${command:azureFunctions.pickProcess}"
"name": "Attach to .NET Functions",
"type": "coreclr",
"request": "attach",
"processId": "${command:azureFunctions.pickProcess}"
}
]
}
]
}
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"azureFunctions.deploySubpath": "src/AzureChronos.Functions/bin/Release/net6.0/publish",
"azureFunctions.deploySubpath": "src/bin/Release/net10.0/publish",

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

azureFunctions.deploySubpath points to src/bin/Release/net10.0/publish, but the Functions project publishes under src/AzureChronos.Functions/bin/Release/net10.0/publish. As written, VS Code deployments will likely pick up a non-existent folder.

Suggested change
"azureFunctions.deploySubpath": "src/bin/Release/net10.0/publish",
"azureFunctions.deploySubpath": "src/AzureChronos.Functions/bin/Release/net10.0/publish",

Copilot uses AI. Check for mistakes.
"azureFunctions.projectLanguage": "C#",
"azureFunctions.projectRuntime": "~4",
"debug.internalConsoleOptions": "neverOpen",
"azureFunctions.projectSubpath": "src/AzureChronos.Functions",
"azureFunctions.preDeployTask": "publish (functions)",
"dotnet.defaultSolution": "src/AzureChronos.sln"

Copilot AI Apr 18, 2026

Copy link

Choose a reason for hiding this comment

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

dotnet.defaultSolution references src/AzureChronos.sln, but this PR removes the .sln and adds src/AzureChronos.slnx. Update the setting (or re-add a .sln) so C# tooling that relies on the default solution doesn't break.

Suggested change
"dotnet.defaultSolution": "src/AzureChronos.sln"
"dotnet.defaultSolution": "src/AzureChronos.slnx"

Copilot uses AI. Check for mistakes.
}
}
Loading