109 lines
4.4 KiB
PHP
109 lines
4.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
function spec439ConstitutionContents(): string
|
|
{
|
|
$path = repo_path('.specify/memory/constitution.md');
|
|
|
|
expect(is_file($path))->toBeTrue();
|
|
|
|
return (string) file_get_contents($path);
|
|
}
|
|
|
|
function spec439ConstitutionUnrelatedBody(string $contents): string
|
|
{
|
|
$withoutSyncImpact = preg_replace(
|
|
'/\A<!--\RSync Impact Report.*?-->\R\R/s',
|
|
'',
|
|
$contents,
|
|
);
|
|
$withoutBranchAmendment = preg_replace(
|
|
'/### Spec-First Workflow\R.*?(?=\R## Quality Gates)/s',
|
|
"### Spec-First Workflow\n[BRANCH-TOPOLOGY-AMENDMENT]\n",
|
|
$withoutSyncImpact,
|
|
);
|
|
$withoutAmendmentMetadata = preg_replace(
|
|
'/^\*\*Version\*\*:.*$/m',
|
|
'**Version**: [AMENDMENT-METADATA]',
|
|
$withoutBranchAmendment,
|
|
);
|
|
|
|
if (! is_string($withoutAmendmentMetadata)) {
|
|
throw new RuntimeException('Unable to normalize Constitution amendment boundaries.');
|
|
}
|
|
|
|
return $withoutAmendmentMetadata;
|
|
}
|
|
|
|
it('defines the three canonical branch roles and fail-closed scope routing', function (): void {
|
|
$contents = spec439ConstitutionContents();
|
|
|
|
expect($contents)
|
|
->toContain(
|
|
'`dev` is the repository-wide integration and promotion branch',
|
|
'`platform-dev` is the integration branch for TenantPilot platform work',
|
|
'`website-dev` is the integration branch for website work',
|
|
'Ordinary platform work MUST branch from, target, and diff against `platform-dev`',
|
|
'ordinary website work MUST branch from, target, and diff against `website-dev`',
|
|
'Cross-stream integration, promotion, and repository-governance work MUST be explicitly classified',
|
|
'Such work MAY target `dev`; the existence of `dev` alone MUST NOT imply that target.',
|
|
'Ambiguous or missing branch-family/ref resolution MUST fail closed.',
|
|
'`origin/dev`, the wrong branch family, `HEAD~1`, or an arbitrary prior commit',
|
|
)
|
|
->not->toContain('- New work branches from `dev` using `feat/<NNN>-<slug>`');
|
|
|
|
expect(substr_count($contents, '### Spec-First Workflow'))->toBe(1);
|
|
});
|
|
|
|
it('keeps the major amendment metadata and Sync Impact report coherent', function (): void {
|
|
$contents = spec439ConstitutionContents();
|
|
|
|
expect($contents)
|
|
->toContain(
|
|
'- Version change: 2.16.0 -> 3.0.0',
|
|
'MAJOR because a normative universal branch rule is redefined',
|
|
'- Added sections: None',
|
|
'- Removed sections: None',
|
|
'✅ `Agents.md`: directly conflicting universal `dev` instructions were',
|
|
'✅ Specs 437 and 438: historical/completed or deferred context remains',
|
|
'**Version**: 3.0.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-07-11',
|
|
)
|
|
->not->toContain('⏳ `Agents.md`');
|
|
|
|
expect(substr_count($contents, 'Sync Impact Report'))->toBe(1)
|
|
->and(substr_count($contents, '**Version**:'))->toBe(1);
|
|
});
|
|
|
|
it('locks all Constitution content outside the authorized amendment boundaries', function (): void {
|
|
$normalized = spec439ConstitutionUnrelatedBody(spec439ConstitutionContents());
|
|
|
|
expect(hash('sha256', $normalized))
|
|
->toBe('9ea4019798f1854e7b513a8fe0bce2529a19207d0b16e81a08582e6b661b8e74');
|
|
});
|
|
|
|
it('keeps authoritative repository branch instructions synchronized', function (): void {
|
|
$instructions = (string) file_get_contents(repo_path('Agents.md'));
|
|
|
|
expect($instructions)
|
|
->toContain(
|
|
'`dev`: repository-weite Integration und Promotion',
|
|
'`platform-dev`: Integration für TenantPilot-Plattformarbeit',
|
|
'`website-dev`: Integration für Website-Arbeit',
|
|
'Normale Plattformarbeit startet von und zielt auf `platform-dev`',
|
|
'Website-Arbeit startet von und zielt auf `website-dev`',
|
|
'Cross-Stream-, Promotion- und Repository-Governance-Arbeit muss ausdrücklich',
|
|
'`dev` darf dabei ein',
|
|
'bewusstes Integrations-/Promotion-Ziel sein, aber kein stiller Fallback.',
|
|
'Für normale Plattformarbeit ist das `origin/platform-dev`',
|
|
'Website-Arbeit `origin/website-dev`',
|
|
'`origin/dev` ist nur für ausdrücklich als',
|
|
)
|
|
->not->toContain(
|
|
'- Default / Integrations-Branch: `dev`',
|
|
'Neue Arbeit läuft über Feature-Branches von `dev`',
|
|
'PR/MR: `feat/...` → `dev`',
|
|
"git merge origin/dev\n",
|
|
);
|
|
});
|