Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tab_width = 2
# New line preferences
end_of_line = lf
insert_final_newline = true
charset = utf-8

# ReSharper properties
resharper_html_attribute_indent = single_indent
Expand All @@ -32,7 +33,6 @@ resharper_check_namespace_highlighting= none

# Development files
[*.{cs,csx,cshtml,csproj,razor,sln,props,targets,json,yml,gitignore}]
charset = "utf-8"
trim_trailing_whitespace = true

#### .NET Coding Conventions ####
Expand Down Expand Up @@ -291,6 +291,11 @@ dotnet_diagnostic.ca1308.severity = none
# ganda repo audit (https://github.com/TimeWarpEngineering/timewarp-ganda)
# ═══════════════════════════════════════════════════════════════════════════════
[ganda.audit]
# This repo is the TimeWarp.SourceGenerators package. A root PackageReference
# would consume the published package from the project that produces it.
# TW0007 is implemented and tested here (task 028). Task 030.
global-usings-analyzer.severity = off

# kebab-path-names prune (additive, basename match):
# .nuget-cache = local package cache (gitignored; package basenames are not ours)
# generated / .generated / build = generator/build outputs
Expand Down
11 changes: 11 additions & 0 deletions .githooks/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>
<!--
Nearest Directory.Build.props wins. Import the repo root, then retarget.
The audit pin TimeWarp.Amuru 3.0.0-beta.1 is net11.0 only, and these hooks
compile inside the repo so they inherit that pin. Task 030.
-->
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions .githooks/post-checkout
33 changes: 33 additions & 0 deletions .githooks/post-checkout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env -S dotnet --
#:package TimeWarp.Amuru
#:package TimeWarp.Amuru.Tools
#:property NoWarn=CA2007
#:property RunAnalyzers=false

// Dispatcher for branch checkouts only ($3 == 1): ganda repo attest.
// Exit 0 always.
using TimeWarp.Amuru;

// git post-checkout <previous> <new> <branch_flag>
// branch_flag is 1 for branch checkout, 0 for file checkout.
if (args.Length < 3 || args[2] != "1")
{
return 0;
}

string? root = Git.FindRoot();
if (root is null)
{
return 0;
}

if (!string.Equals(Environment.GetEnvironmentVariable("GANDA_ATTEST_HOOK"), "0", StringComparison.Ordinal))
{
await Shell.Builder("ganda")
.WithArguments("repo", "attest")
.WithWorkingDirectory(root)
.WithNoValidation()
.RunAsync();
}

return 0;
19 changes: 13 additions & 6 deletions .githooks/post-commit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#:package TimeWarp.Amuru
#:package TimeWarp.Amuru.Tools
#:property NoWarn=CA2007
#:property RunAnalyzers=false

// Dispatcher: ganda repo attest.
// Exit 0 always — commit already landed.
using TimeWarp.Amuru;

string? root = Git.FindRoot();
Expand All @@ -11,9 +14,13 @@
return 0;
}

await Shell.Builder("ganda")
.WithArguments("memsearch", "index-repo", "--background")
.WithWorkingDirectory(root)
.WithNoValidation()
.RunAsync();
return 0;
if (!string.Equals(Environment.GetEnvironmentVariable("GANDA_ATTEST_HOOK"), "0", StringComparison.Ordinal))
{
await Shell.Builder("ganda")
.WithArguments("repo", "attest")
.WithWorkingDirectory(root)
.WithNoValidation()
.RunAsync();
}

return 0;
19 changes: 13 additions & 6 deletions .githooks/post-merge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#:package TimeWarp.Amuru
#:package TimeWarp.Amuru.Tools
#:property NoWarn=CA2007
#:property RunAnalyzers=false

// Dispatcher: ganda repo attest.
// Exit 0 always — cannot undo an already-completed merge.
using TimeWarp.Amuru;

string? root = Git.FindRoot();
Expand All @@ -11,9 +14,13 @@
return 0;
}

await Shell.Builder("ganda")
.WithArguments("memsearch", "index-repo", "--background")
.WithWorkingDirectory(root)
.WithNoValidation()
.RunAsync();
return 0;
if (!string.Equals(Environment.GetEnvironmentVariable("GANDA_ATTEST_HOOK"), "0", StringComparison.Ordinal))
{
await Shell.Builder("ganda")
.WithArguments("repo", "attest")
.WithWorkingDirectory(root)
.WithNoValidation()
.RunAsync();
}

return 0;
1 change: 1 addition & 0 deletions .githooks/pre-commit
34 changes: 34 additions & 0 deletions .githooks/pre-commit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env -S dotnet --
#:package TimeWarp.Amuru
#:package TimeWarp.Amuru.Tools
#:property NoWarn=CA2007;CA1849;RS0030
#:property RunAnalyzers=false

// Refuse commits while HEAD is master or main.
// Escape hatch: git commit --no-verify
using TimeWarp.Amuru;

string? root = Git.FindRoot();
if (root is null)
{
return 0;
}

CommandOutput revParse = await Shell.Builder("git")
.WithArguments("rev-parse", "--abbrev-ref", "HEAD")
.WithWorkingDirectory(root)
.WithNoValidation()
.CaptureAsync();

string branch = revParse.Stdout.Trim();
if (branch is "master" or "main")
{
#pragma warning disable RS0030, CA1849 // hook runfile: stderr to git, no ITerminal host
Console.Error.WriteLine($"Refusing commit on '{branch}'.");
Console.Error.WriteLine("Do not commit on master/main — use a feature worktree/branch (e.g. ganda worktree / task branch).");
Console.Error.WriteLine("Escape hatch (intentional only): git commit --no-verify");
#pragma warning restore RS0030, CA1849
return 1;
}

return 0;
1 change: 1 addition & 0 deletions .githooks/pre-push
104 changes: 104 additions & 0 deletions .githooks/pre-push.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env -S dotnet --
#:package TimeWarp.Amuru
#:package TimeWarp.Amuru.Tools
#:property NoWarn=CA2007;CA1849;RS0030
#:property RunAnalyzers=false

// Refuse pushes that update home branches (master/main) while HEAD is master or main.
// Allow other dests (feature/*, etc.) so origin-home can publish a missing --into ref.
// Allow refs/tags/* (release tags) and refs/ganda/* (claims CAS) when every dest is one of those.
// Mixed tag + branch batches stay refused while HEAD is home.
// An empty dest list updates no branch. Git omits refs that are already up to date and
// refs it has already rejected as non-fast-forward, and runs this hook on what remains.
// Refusing that empty list hides the non-fast-forward error, so the shared claims push
// (pr merge cleanup and worktree gc) cannot retry while HEAD is master.
// A branch update lists its dest.
// Refuse pushing a local task/* branch to a home branch from any HEAD (a task worktree that
// still tracks origin/<home>). Raw-sha sources (kanban publish merge commit) stay allowed.
// Commits on master stay blocked by pre-commit. Escape hatch: git push --no-verify
using TimeWarp.Amuru;

string? root = Git.FindRoot();
if (root is null)
{
return 0;
}

// stdin: <local ref> <local sha> <remote ref> <remote sha> (one line per dest)
List<string> remoteRefs = [];
List<string> taskToHome = [];
string? line;
while ((line = Console.In.ReadLine()) is not null)
{
if (string.IsNullOrWhiteSpace(line))
continue;

string[] parts = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3)
{
remoteRefs.Add(parts[2]);
if (parts[0].StartsWith("refs/heads/task/", StringComparison.Ordinal) && IsHomeBranchDest(parts[2]))
taskToHome.Add($"{parts[0]["refs/heads/".Length..]} -> {parts[2]["refs/heads/".Length..]}");
}
}

if (taskToHome.Count > 0)
{
#pragma warning disable RS0030, CA1849 // hook runfile: stderr to git, no ITerminal host
foreach (string push in taskToHome)
Console.Error.WriteLine($"Refusing push of task branch to home: {push}.");
Console.Error.WriteLine("Task branches publish to origin/<task branch> and land on home via PR.");
Console.Error.WriteLine("Fix tracking: ganda repo audit --fix --checks task-branch-upstream, then git push.");
Console.Error.WriteLine("Escape hatch (intentional only): git push --no-verify");
#pragma warning restore RS0030, CA1849
return 1;
}

// Nothing in this push will update a ref. Allow it so git can report
// "everything up-to-date" or the non-fast-forward rejection it already decided.
if (remoteRefs.Count == 0)
{
return 0;
}

if (remoteRefs.All(IsExemptDest))
{
return 0;
}

CommandOutput revParse = await Shell.Builder("git")
.WithArguments("rev-parse", "--abbrev-ref", "HEAD")
.WithWorkingDirectory(root)
.WithNoValidation()
.CaptureAsync();

string branch = revParse.Stdout.Trim();
if (branch is "master" or "main")
{
bool updatesHome = remoteRefs.Any(IsHomeBranchDest);
bool mixesTagAndBranch = remoteRefs.Any(IsTagDest) && remoteRefs.Any(IsBranchDest);
if (updatesHome || mixesTagAndBranch)
{
#pragma warning disable RS0030, CA1849 // hook runfile: stderr to git, no ITerminal host
Console.Error.WriteLine($"Refusing push while HEAD is '{branch}'.");
Console.Error.WriteLine("Do not push from master/main — use a feature worktree/branch (e.g. ganda worktree / task branch).");
Console.Error.WriteLine("Escape hatch (intentional only): git push --no-verify");
#pragma warning restore RS0030, CA1849
return 1;
}
}

return 0;

static bool IsExemptDest(string remoteRef) =>
IsTagDest(remoteRef)
|| remoteRef.StartsWith("refs/ganda/", StringComparison.Ordinal);

static bool IsTagDest(string remoteRef) =>
remoteRef.StartsWith("refs/tags/", StringComparison.Ordinal);

static bool IsBranchDest(string remoteRef) =>
remoteRef.StartsWith("refs/heads/", StringComparison.Ordinal);

static bool IsHomeBranchDest(string remoteRef) =>
remoteRef is "refs/heads/master" or "refs/heads/main";
8 changes: 6 additions & 2 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
- 'source/Directory.Build.props'
- 'nuget.config'
pull_request:
branches: [master]
branches: [master, 'feature/**']
paths:
- 'source/**'
- 'tests/**'
Expand Down Expand Up @@ -49,7 +49,11 @@ jobs:
fetch-depth: 0
- uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
# Dev CLI targets net11 because the audit pin TimeWarp.Amuru 3.0.0-beta.1 is net11-only.
# Net10 stays installed so the test projects still build. Task 030.
dotnet-version: |
10.0.x
11.0.100-rc.3.26522.148

# Prefer NuGet Trusted Publishing (OIDC). Fall back to org API key when no trust
# policy exists yet for this package (common on first release under a new policy).
Expand Down
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,34 @@ __pycache__/
/.aider.tags.cache.v3/cache.db-wal
/.aider.input.history
.nuget-cache/

# Routine journals, mission briefs, and progress logs beside kitchens (local; not product)
*.journal.json
stacked-task-set*.resume.json
*.next.md
*.progress.log

# Review-round markers beside kitchens (local; not product)
**/review/round-*/base.sha
**/review/round-*/closed

# Herdr pane-command oracle logs beside kitchens (local; not product)
oracle-*.log

# Task-work host progress lines beside kitchens (local; not product)
task-work.progress.log

# Mission heartbeat in the claim worktree (local; not product)
mission.heartbeat

# VS Code settings: Peacock rewrites this file; vscode-window-icon --fix writes it locally (not tracked)
.vscode/settings.json

# Per-user tool state (XDG .local/, e.g. gh device-id; local; not product)
.local/
# AOT dev CLI (self-install output; local; not product)
bin/dev
# AOT dev CLI debug companion (self-install output; local; not product)
bin/dev.dbg
# AOT dev CLI on Windows (self-install output; local; not product)
bin/dev.exe
50 changes: 0 additions & 50 deletions .memsearch.toml

This file was deleted.

Loading
Loading