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
9 changes: 5 additions & 4 deletions local-docs/source-of-truth.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Status values:
| 11 | Routing source reference (checklist prose deferred) | workflows/addition-intake.md | pin | policy-contracts.ps1 | consolidated |
| 12 | Glossary terms | glossary.md | link | editorial convention | accepted |
| 13 | Shared runtime-global prose | manifests/policy-contracts.json | pin | policy-contracts.ps1 | consolidated |
| 14 | Portable runtime home paths | scripts/common.ps1 home resolvers | pin | portable-home-paths.ps1 | consolidated |
Comment thread
ariobarin marked this conversation as resolved.

## Intentional separations

Expand Down Expand Up @@ -101,14 +102,14 @@ audits do not re-flag them.

## Status summary

Of the 13 fact families, 12 are consolidated or already canonical and 1 is
Of the 14 fact families, 13 are consolidated or already canonical and 1 is
accepted as an intentional separation (cluster 12 glossary). The
surface-specific update-together checklist prose (part of cluster 11) is
deferred as editorial work. The consolidations are
enforced mechanically: new manifests and doctor checks (`retired-skills`,
`model-tiers`, `generated-artifacts`, `source-of-truth`, `template-anchors`),
generators under `scripts/generators/`, policy-contract pins, git-derived
required files, and dynamic doctor dispatch.
`model-tiers`, `generated-artifacts`, `source-of-truth`, `template-anchors`,
`portable-home-paths`), generators under `scripts/generators/`, policy-contract
pins, git-derived required files, and dynamic doctor dispatch.

Required files (cluster 4) use the git index as the source of repository
membership. The architecturally mandatory entrypoints the old hand-written list
Expand Down
1 change: 1 addition & 0 deletions manifests/doctor-checks.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"manifest-boundaries.ps1",
"model-tiers.ps1",
"policy-contracts.ps1",
"portable-home-paths.ps1",
"required-files.ps1",
"restart-recovery.ps1",
"retired-plugins.ps1",
Expand Down
4 changes: 2 additions & 2 deletions manifests/portable-files.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ derived_skills = [
"write-a-skill",
]

# These reviewer agents are maintained explicitly because their Claude
# frontmatter and Codex read-only sandbox contracts are platform-specific.
# No Claude agent is maintained as a separate source. Every Claude agent
# derives from codex/agents/*.toml at install time; see derived_agents below.
agents = []

# Other Claude agents derive from codex/agents/<name>.toml at install time.
Expand Down
8 changes: 8 additions & 0 deletions scripts/doctor/checks/claude.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,12 @@ else {
$problems.Add("claude derived agent missing frontmatter entry: $agent")
}
}

# Reverse binding: every frontmatter entry must be a declared derived
# agent, so an orphan left behind after removing a derived agent fails.
foreach ($agent in $script:ClaudeDerivedAgentFrontmatter.Keys) {
if ($manifestClaudeDerivedAgents -notcontains $agent) {
$problems.Add("claude frontmatter entry is not a declared derived agent: $agent")
}
}
}
146 changes: 146 additions & 0 deletions scripts/doctor/checks/portable-home-paths.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Home paths are executable truth in scripts/common.ps1 (Get-CodexHome,
# Get-AgentsHome, Get-ClaudeHome). The manifest, launchers, operator docs, and
# focused workflow docs repeat those defaults for different audiences. This
# check derives every expected copy from common.ps1 so a default change cannot
# leave an accepted runtime route or current instruction stale.
$paths = @{
common = Join-Path $repoRoot "scripts\common.ps1"
manifest = Join-Path $repoRoot "manifests\portable-files.toml"
portableWorkflow = Join-Path $repoRoot "workflows\portable-config.md"
claudeWorkflow = Join-Path $repoRoot "workflows\claude-config.md"
whichLlmWorkflow = Join-Path $repoRoot "workflows\which-llm-skill.md"
readme = Join-Path $repoRoot "README.md"
contributing = Join-Path $repoRoot "CONTRIBUTING.md"
doctor = Join-Path $repoRoot "scripts\doctor.ps1"
hookPowerShell = Join-Path $repoRoot "codex\hooks\portable_guard.ps1"
hookShell = Join-Path $repoRoot "codex\hooks\portable_guard.sh"
hooksJson = Join-Path $repoRoot "codex\hooks.json"
hooksReadme = Join-Path $repoRoot "codex\hooks\README.md"
}

$missing = @($paths.GetEnumerator() | Where-Object { -not (Test-Path -LiteralPath $_.Value -PathType Leaf) })
if ($missing.Count -gt 0) {
foreach ($entry in $missing) {
$problems.Add("missing $($entry.Value) for home path check")
}
}
else {
function Get-PortableManifestString {
param($Manifest, $Section, $Key)
$sectionValue = Get-PortableJsonProperty -Object $Manifest -Name $Section
return Get-PortableJsonProperty -Object $sectionValue -Name $Key
}

function Assert-Equals {
param($Actual, $Expected, [string]$Label)
if ($null -eq $Actual) {
$problems.Add("$Label is missing")
}
elseif ($Actual -cne $Expected) {
$problems.Add("$Label documents '$Actual' but expected '$Expected'")
}
}

function Normalize-PortableWhitespace {
param([string]$Text)
return [regex]::Replace($Text, '\s+', ' ').Trim()
}

function Assert-Contains {
param([string]$Text, [string]$Expected, [string]$Label)
if ($Text.IndexOf($Expected, [System.StringComparison]::Ordinal) -lt 0) {
$problems.Add("$Label is missing or stale: $Expected")
}
}

function Assert-NormalizedContains {
param([string]$Text, [string]$Expected, [string]$Label)
$normalizedText = Normalize-PortableWhitespace $Text
$normalizedExpected = Normalize-PortableWhitespace $Expected
if ($normalizedText.IndexOf($normalizedExpected, [System.StringComparison]::Ordinal) -lt 0) {
$problems.Add("$Label is missing or stale: $normalizedExpected")
}
}

function Assert-MatchCount {
param([string]$Text, [string]$Expected, [int]$Count, [string]$Label)
$actual = [regex]::Matches($Text, [regex]::Escape($Expected)).Count
if ($actual -ne $Count) {
$problems.Add("$Label expected $Count copies of '$Expected' but found $actual")
}
}

$common = Get-Content -Raw -LiteralPath $paths.common
$codexMatch = [regex]::Match($common, 'Join-Path \$env:USERPROFILE "([^"]+)"')
$homeMatches = [regex]::Matches($common, 'Join-Path \$HOME "([^"]+)"')
if (-not $codexMatch.Success -or $homeMatches.Count -lt 2) {
$problems.Add("could not extract home directory defaults from common.ps1")
}
else {
$defaults = @{
codex = $codexMatch.Groups[1].Value
agents = $homeMatches[0].Groups[1].Value
claude = $homeMatches[1].Groups[1].Value
}
$manifest = (Get-PortableGeneratedData).manifest

$codexHome = '$CODEX_HOME or %USERPROFILE%\' + $defaults.codex
$agentsHome = '$HOME\' + $defaults.agents
$claudeHome = '$HOME\' + $defaults.claude
$codexTilde = '~/' + $defaults.codex
$claudeTilde = '~/' + $defaults.claude
$agentsSlash = '$HOME/' + $defaults.agents
$claudeSlash = '$HOME/' + $defaults.claude

Assert-Equals (Get-PortableManifestString $manifest "codex" "home") $codexHome "portable manifest [codex].home"
Assert-Equals (Get-PortableManifestString $manifest "agents" "home") $agentsHome "portable manifest [agents].home"
Assert-Equals (Get-PortableManifestString $manifest "agents" "skills_dir") ($agentsHome + "\skills") "portable manifest [agents].skills_dir"
Assert-Equals (Get-PortableManifestString $manifest "claude" "home") $claudeHome "portable manifest [claude].home"
Assert-Equals (Get-PortableManifestString $manifest "claude" "skills_dir") ($claudeHome + "\skills") "portable manifest [claude].skills_dir"
Assert-Equals (Get-PortableManifestString $manifest "claude" "agents_dir") ($claudeHome + "\agents") "portable manifest [claude].agents_dir"

$portableWorkflow = Get-Content -Raw -LiteralPath $paths.portableWorkflow
Assert-Contains $portableWorkflow ('- `-CodexHome`, then `$env:CODEX_HOME`, then `%USERPROFILE%\' + $defaults.codex + '`;') "portable-config Codex resolution"
Assert-Contains $portableWorkflow ('- `-AgentsHome`, then `$HOME\' + $defaults.agents + '`;') "portable-config agents resolution"
Assert-Contains $portableWorkflow ('- `-ClaudeHome`, then `$HOME\' + $defaults.claude + '`.') "portable-config Claude resolution"

$readme = Get-Content -Raw -LiteralPath $paths.readme
$readmeResolution = 'Scripts use `-CodexHome`, then `$env:CODEX_HOME`, then `%USERPROFILE%\' + $defaults.codex + '`; `-AgentsHome`, then `$HOME\' + $defaults.agents + '`; and `-ClaudeHome`, then `$HOME\' + $defaults.claude + '`.'
Assert-NormalizedContains $readme $readmeResolution "README home resolution"
Assert-Contains $readme ($codexTilde + "/AGENTS.md") "README Codex install root"
Assert-Contains $readme ($claudeTilde + "/CLAUDE.md") "README Claude install root"
Assert-Contains $readme ($agentsSlash + "/skills") "README agents skill root"
Assert-Contains $readme ($claudeSlash + "/skills") "README Claude skill root"

$contributing = Get-Content -Raw -LiteralPath $paths.contributing
Assert-Contains $contributing ($agentsSlash + "/skills") "CONTRIBUTING agents skill root"

$claudeWorkflow = Get-Content -Raw -LiteralPath $paths.claudeWorkflow
Assert-Contains $claudeWorkflow ($claudeSlash + "/CLAUDE.md") "Claude workflow instruction root"
Assert-Contains $claudeWorkflow $claudeSlash "Claude workflow default root"

$whichLlmWorkflow = Get-Content -Raw -LiteralPath $paths.whichLlmWorkflow
Assert-Contains $whichLlmWorkflow ($agentsHome + "\skills\which-llm\pick.py") "which-llm installed pick path"
Assert-Contains $whichLlmWorkflow ($agentsHome + "\skills\which-llm\query.py") "which-llm installed query path"

$doctor = Get-Content -Raw -LiteralPath $paths.doctor
Assert-Contains $doctor ('$liveHome = Join-Path $env:USERPROFILE "' + $defaults.codex + '"') "doctor Codex fallback"
Assert-Contains $doctor ('$agentsHomePath = Join-Path $HOME "' + $defaults.agents + '"') "doctor agents fallback"
Assert-Contains $doctor ('$claudeHomePath = Join-Path $HOME "' + $defaults.claude + '"') "doctor Claude fallback"

$hookPowerShell = Get-Content -Raw -LiteralPath $paths.hookPowerShell
Assert-Contains $hookPowerShell ('$codexHome = Join-Path $env:USERPROFILE "' + $defaults.codex + '"') "PowerShell hook Codex fallback"

$hookShell = Get-Content -Raw -LiteralPath $paths.hookShell
Assert-Contains $hookShell ('codex_home=$HOME/' + $defaults.codex) "shell hook Codex fallback"

$hooksJson = Get-Content -Raw -LiteralPath $paths.hooksJson
$hookShellDefault = '${CODEX_HOME:-$HOME/' + $defaults.codex + '}/hooks/portable_guard.sh'
$hookWindowsDefault = "Join-Path `$env:USERPROFILE '$($defaults.codex)'"
Assert-MatchCount $hooksJson $hookShellDefault 2 "hook JSON shell Codex fallback"
Assert-MatchCount $hooksJson $hookWindowsDefault 2 "hook JSON Windows Codex fallback"

$hooksReadme = Get-Content -Raw -LiteralPath $paths.hooksReadme
Assert-Contains $hooksReadme $codexTilde "hooks README Codex default"
}
}
2 changes: 1 addition & 1 deletion scripts/doctor/checks/source-of-truth.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ else {
}
}

foreach ($expected in 1..13) {
foreach ($expected in 1..14) {
if ($seenIds -notcontains "$expected") {
$problems.Add("source-of-truth register is missing cluster row: $expected")
}
Expand Down