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
58 changes: 57 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,62 @@ composer test -- --filter=ClassName # Single test
./vendor/bin/pint --dirty # Format changed files
```

## Local Package Linking

Most packages depend on `host-uk/core` (the published name of `core-php`). Since these are private packages, `composer install` fails unless you configure local path repositories.

### Quick Setup

```bash
# macOS/Linux - setup all packages
./scripts/setup-local-packages.sh

# Windows (PowerShell)
.\scripts\setup-local-packages.ps1

# Or setup a specific package
./scripts/setup-local-packages.sh core-tenant
```
Comment on lines +140 to +149
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and examine the setup script
fd setup-local-packages.sh

Repository: host-uk/core-devops

Length of output: 94


🏁 Script executed:

# Read CLAUDE.md to see the context around lines 140-149
wc -l CLAUDE.md && sed -n '130,160p' CLAUDE.md

Repository: host-uk/core-devops

Length of output: 873


🏁 Script executed:

# Search for any existing jq mentions in CLAUDE.md
rg -n "jq" CLAUDE.md

Repository: host-uk/core-devops

Length of output: 45


🏁 Script executed:

# Read the setup script
cat scripts/setup-local-packages.sh

Repository: host-uk/core-devops

Length of output: 3669


Add a short jq prerequisite note for macOS/Linux.

The setup script requires jq for JSON manipulation, but the Quick Setup section doesn't mention it. Add a brief prerequisite line to prevent users from encountering errors.

📚 Suggested doc tweak
 ### Quick Setup

+macOS/Linux requires `jq` for JSON merging (install via `brew install jq` or your package manager).
+
 ```bash
 # macOS/Linux - setup all packages
 ./scripts/setup-local-packages.sh
🤖 Prompt for AI Agents
In `@CLAUDE.md` around lines 140 - 149, Add a short prerequisite note to the Quick
Setup section stating that jq is required on macOS/Linux before running
./scripts/setup-local-packages.sh (or any call to that script), so users install
jq (e.g., via brew or apt) to avoid JSON parsing errors; update the text near
the macOS/Linux example and mention jq is only required for macOS/Linux
invocations of ./scripts/setup-local-packages.sh.


### Installing Dependencies

After running the setup script, install dependencies using the local config:

```bash
# macOS/Linux
cd packages/core-tenant
COMPOSER=composer.local.json composer install

# Windows (PowerShell)
cd packages\core-tenant
$env:COMPOSER='composer.local.json'; composer install
```

### How It Works

The setup script generates a `composer.local.json` for each package by merging the original `composer.json` with path repositories pointing to sibling packages:

```json
{
"name": "host-uk/core-tenant",
"require": {"host-uk/core": "dev-main"},
"repositories": [
{"type": "path", "url": "../core-php", "options": {"symlink": true}},
{"type": "path", "url": "../core-tenant", "options": {"symlink": true}}
]
}
```

When Composer sees `host-uk/core` as a requirement, it resolves it to `../core-php` (symlinked) instead of trying to fetch from Packagist.

### Packages That Need This

Only `core-php` can be tested without local linking (it has no `host-uk/*` dependencies). All other packages require this setup:

- core-tenant, core-admin, core-api, core-mcp, core-agentic
- core-bio, core-social, core-analytics, core-notify, core-trust, core-support
- core-commerce, core-content, core-tools, core-uptelligence, core-developer

## Package Switching Workflow

To work on a different package without navigating:
Expand Down Expand Up @@ -176,6 +232,6 @@ Defined in `repos.yaml`:

Don't add application code here. This repo only contains:
- `repos.yaml` - Package registry with dependencies
- `scripts/` - Cross-platform setup scripts (install-deps, install-core)
- `scripts/` - Cross-platform setup scripts (install-deps, install-core, clone-repos, setup-local-packages)
- `Makefile` - Setup orchestration
- `.core/` - Workspace configuration and Claude Code integration
118 changes: 118 additions & 0 deletions scripts/setup-local-packages.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Setup local package linking for PHP dependency testing
# Usage: .\scripts\setup-local-packages.ps1 [package-name]
# Example: .\scripts\setup-local-packages.ps1 core-tenant

param(
[string]$PackageName
)

$ErrorActionPreference = "Stop"

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RootDir = Split-Path -Parent $ScriptDir
$PackagesDir = Join-Path $RootDir "packages"

function Write-Info { param($msg) Write-Host "[INFO] $msg" -ForegroundColor Green }
function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow }
function Write-Step { param($msg) Write-Host "[STEP] $msg" -ForegroundColor Cyan }

# Path repositories to inject
$PathRepositories = @(
@{type = "path"; url = "../core-php"; options = @{symlink = $true}}
@{type = "path"; url = "../core-tenant"; options = @{symlink = $true}}
@{type = "path"; url = "../core-admin"; options = @{symlink = $true}}
@{type = "path"; url = "../core-api"; options = @{symlink = $true}}
@{type = "path"; url = "../core-mcp"; options = @{symlink = $true}}
@{type = "path"; url = "../core-agentic"; options = @{symlink = $true}}
@{type = "path"; url = "../core-commerce"; options = @{symlink = $true}}
@{type = "path"; url = "../core-content"; options = @{symlink = $true}}
@{type = "path"; url = "../core-tools"; options = @{symlink = $true}}
)

function Setup-Package {
param([string]$pkg)

$pkgDir = Join-Path $PackagesDir $pkg
$composerJson = Join-Path $pkgDir "composer.json"
$target = Join-Path $pkgDir "composer.local.json"

if (-not (Test-Path $pkgDir)) {
Write-Warn "Package directory not found: $pkgDir"
return
}

if (-not (Test-Path $composerJson)) {
Write-Warn "No composer.json found in $pkg (skipping)"
return
}

if (Test-Path $target) {
Write-Warn "composer.local.json already exists in $pkg (skipping)"
return
}

Write-Step "Setting up $pkg..."

# Read original composer.json
$original = Get-Content $composerJson -Raw | ConvertFrom-Json

# Add repositories array (path repos must come first)
$original | Add-Member -NotePropertyName "repositories" -NotePropertyValue $PathRepositories -Force

# Write merged composer.local.json (UTF-8 without BOM for Composer compatibility)
$json = $original | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($target, $json, [System.Text.UTF8Encoding]::new($false))
Comment on lines +59 to +64
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n scripts/setup-local-packages.ps1

Repository: host-uk/core-devops

Length of output: 4965


Preserve any existing Composer repositories when generating composer.local.json.

Overwriting repositories can drop required VCS/private entries and break installs for packages that already define them. Merge path repos ahead of existing ones instead.

🛠️ Suggested merge (path repos first)
-    $original | Add-Member -NotePropertyName "repositories" -NotePropertyValue $PathRepositories -Force
+    $existing = @()
+    if ($original.PSObject.Properties.Name -contains "repositories") {
+        $existing = $original.repositories
+    }
+    $merged = @($PathRepositories + $existing)
+    $original | Add-Member -NotePropertyName "repositories" -NotePropertyValue $merged -Force
🤖 Prompt for AI Agents
In `@scripts/setup-local-packages.ps1` around lines 59 - 64, The current script
unconditionally overwrites the "repositories" property, which drops existing
Composer VCS/private entries; update the logic that sets $original.repositories
so it merges $PathRepositories before any existing $original.repositories (i.e.,
if $original.repositories exists, create a new array with $PathRepositories
elements followed by the existing entries, otherwise set it to
$PathRepositories), then proceed to ConvertTo-Json and WriteAllText($target,
...) as before using the UTF‑8 no‑BOM encoding; reference the symbols $original,
$PathRepositories, "repositories", ConvertTo-Json and
[System.IO.File]::WriteAllText to locate and modify the code.


# Add to .gitignore if not already there
$gitignore = Join-Path $pkgDir ".gitignore"
if (Test-Path $gitignore) {
$content = Get-Content $gitignore -Raw
if ($content -notmatch "composer\.local\.json") {
Add-Content $gitignore "composer.local.json"
}
} else {
"composer.local.json" | Out-File $gitignore -Encoding utf8
}

Write-Info "Created $target"
}

# Main
if ($PackageName) {
# Setup specific package
Setup-Package $PackageName
} else {
# Setup all packages that depend on host-uk/core
Write-Info "Setting up local package linking for all packages..."

$packages = Get-ChildItem -Path $PackagesDir -Directory

foreach ($dir in $packages) {
$pkg = $dir.Name

# Skip packages that don't need it
if ($pkg -eq "core-php" -or $pkg -eq "core-template") {
continue
}

# Check if composer.json exists and has host-uk dependencies
$composerJson = Join-Path $dir.FullName "composer.json"
if (Test-Path $composerJson) {
$content = Get-Content $composerJson -Raw
if ($content -match '"host-uk/') {
Setup-Package $pkg
}
}
}
}

Write-Host ""
Write-Info "Done! To install dependencies with local packages:"
Write-Host ""
Write-Host " cd packages\<package>"
Write-Host " `$env:COMPOSER='composer.local.json'; composer install"
Write-Host ""
Write-Host "Or on a single line:"
Write-Host ""
Write-Host " powershell -Command `"`$env:COMPOSER='composer.local.json'; composer install`""
Write-Host ""
115 changes: 115 additions & 0 deletions scripts/setup-local-packages.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# Setup local package linking for PHP dependency testing
# Usage: ./scripts/setup-local-packages.sh [package-name]
# Example: ./scripts/setup-local-packages.sh core-tenant

set -e

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PACKAGES_DIR="$ROOT_DIR/packages"

# Colour output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No colour

info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
step() { echo -e "${CYAN}[STEP]${NC} $1"; }

# Path repositories JSON to inject
REPOSITORIES='[
{"type": "path", "url": "../core-php", "options": {"symlink": true}},
{"type": "path", "url": "../core-tenant", "options": {"symlink": true}},
{"type": "path", "url": "../core-admin", "options": {"symlink": true}},
{"type": "path", "url": "../core-api", "options": {"symlink": true}},
{"type": "path", "url": "../core-mcp", "options": {"symlink": true}},
{"type": "path", "url": "../core-agentic", "options": {"symlink": true}},
{"type": "path", "url": "../core-commerce", "options": {"symlink": true}},
{"type": "path", "url": "../core-content", "options": {"symlink": true}},
{"type": "path", "url": "../core-tools", "options": {"symlink": true}}
]'

# Check for jq (required for JSON manipulation)
if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed. Install it with: brew install jq"
exit 1
Comment on lines +35 to +38
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate and examine the setup-local-packages.sh file
find . -name "setup-local-packages.sh" -type f 2>/dev/null | head -5

Repository: host-uk/core-devops

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Read the setup-local-packages.sh file with line numbers
cat -n ./scripts/setup-local-packages.sh

Repository: host-uk/core-devops

Length of output: 4474


Provide cross-platform installation guidance for jq.

The error message at line 37 references only brew install jq, which is misleading for Linux and Windows users. Provide guidance for all supported platforms.

📝 Suggested fix
# Check for jq (required for JSON manipulation)
if ! command -v jq &> /dev/null; then
-    echo "Error: jq is required but not installed. Install it with: brew install jq"
+    echo "Error: jq is required but not installed."
+    echo "Install: macOS (brew install jq) | Debian/Ubuntu (sudo apt-get install jq) | Fedora (sudo dnf install jq)"
     exit 1
 fi

Per coding guidelines: scripts/** must ensure cross-platform compatibility (macOS, Linux, Windows).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check for jq (required for JSON manipulation)
if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed. Install it with: brew install jq"
exit 1
# Check for jq (required for JSON manipulation)
if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed."
echo "Install: macOS (brew install jq) | Debian/Ubuntu (sudo apt-get install jq) | Fedora (sudo dnf install jq)"
exit 1
fi
🤖 Prompt for AI Agents
In `@scripts/setup-local-packages.sh` around lines 35 - 38, The jq presence check
(the if ! command -v jq &> /dev/null block) only tells users to run "brew
install jq", which is macOS-specific; update the error message to show
cross-platform install guidance for macOS (brew), common Linux package managers
(apt-get, yum/dnf, pacman) and Windows (choco or scoop) and optionally point to
installing via WSL or the jq binary download. Modify the echo in that block to
include these platform-specific commands and a short note to choose the
appropriate installer for their OS so users on Linux and Windows get correct
instructions.

fi

# Setup function for a single package
setup_package() {
local pkg="$1"
local pkg_dir="$PACKAGES_DIR/$pkg"
local composer_json="$pkg_dir/composer.json"
local target="$pkg_dir/composer.local.json"

if [[ ! -d "$pkg_dir" ]]; then
warn "Package directory not found: $pkg_dir"
return 1
fi

if [[ ! -f "$composer_json" ]]; then
warn "No composer.json found in $pkg (skipping)"
return 0
fi

if [[ -f "$target" ]]; then
warn "composer.local.json already exists in $pkg (skipping)"
return 0
fi

step "Setting up $pkg..."

# Merge repositories into composer.json and write to composer.local.json
jq --argjson repos "$REPOSITORIES" '. + {repositories: $repos}' "$composer_json" > "$target"

Comment on lines +65 to +67
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Read the setup script to examine lines 65-67 and surrounding context
wc -l scripts/setup-local-packages.sh

Repository: host-uk/core-devops

Length of output: 98


🏁 Script executed:

# View the file around lines 65-67 with context
sed -n '50,80p' scripts/setup-local-packages.sh | cat -n

Repository: host-uk/core-devops

Length of output: 1106


🏁 Script executed:

# Also check what REPOSITORIES variable contains and how it's defined
cat scripts/setup-local-packages.sh | cat -n

Repository: host-uk/core-devops

Length of output: 4474


Merge path repositories before existing entries instead of replacing them.

The current jq command completely overwrites any pre-existing repositories configuration in composer.json when creating composer.local.json. If the original composer.json defines VCS or private sources, they will be lost when using COMPOSER=composer.local.json composer install, causing dependency resolution to fail.

🛠️ Suggested fix
-    jq --argjson repos "$REPOSITORIES" '. + {repositories: $repos}' "$composer_json" > "$target"
+    jq --argjson repos "$REPOSITORIES" '.repositories = ($repos + (.repositories // []))' "$composer_json" > "$target"
🤖 Prompt for AI Agents
In `@scripts/setup-local-packages.sh` around lines 65 - 67, The jq invocation that
writes composer.local.json currently replaces any existing .repositories with
$REPOSITORIES; change it to merge the new repositories into the existing
.repositories (prepending $REPOSITORIES before existing entries) instead of
overwriting so VCS/private sources are preserved. Update the jq expression used
with variables composer_json, REPOSITORIES and target to read the original
.repositories (or use an empty array if missing) and produce a combined array
with $REPOSITORIES first, then the original entries, then write to "$target".
Ensure the updated command still handles missing .repositories gracefully.

# Add to .gitignore if not already there
local gitignore="$pkg_dir/.gitignore"
if [[ -f "$gitignore" ]]; then
if ! grep -q "composer.local.json" "$gitignore"; then
echo "composer.local.json" >> "$gitignore"
fi
else
echo "composer.local.json" > "$gitignore"
fi

info "Created $target"
}

# Main
if [[ -n "$1" ]]; then
# Setup specific package
setup_package "$1"
else
# Setup all packages that depend on host-uk/core
info "Setting up local package linking for all packages..."

for dir in "$PACKAGES_DIR"/*/; do
pkg="$(basename "$dir")"

# Skip packages that don't need it (core-php has no deps, core-template is a project)
if [[ "$pkg" == "core-php" || "$pkg" == "core-template" ]]; then
continue
fi

# Check if composer.json exists and has host-uk dependencies
if [[ -f "$dir/composer.json" ]]; then
if grep -q '"host-uk/' "$dir/composer.json"; then
setup_package "$pkg"
fi
fi
done
fi

echo ""
info "Done! To install dependencies with local packages:"
echo ""
echo " cd packages/<package>"
echo " COMPOSER=composer.local.json composer install"
echo ""
echo "Or use the dev:packages script if available:"
echo ""
echo " composer dev:packages"
echo ""
Loading