diff --git a/phpunit.xml b/phpunit.xml
index 0d6f653..337cec8 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -74,8 +74,12 @@
tests/Architecture/ApiSchemaContractTest.php
- tests/Architecture/AgentSystemContractTest.php
- tests/Architecture/CodexSkillsContractTest.php
+ tests/Architecture/AgentSystemCatalogContractTest.php
+ tests/Architecture/AgentSystemFileContractTest.php
+ tests/Architecture/AgentSystemReferenceContractTest.php
+ tests/Architecture/CodexAutomationContractTest.php
+ tests/Architecture/CodexDocsIntegrityContractTest.php
+ tests/Architecture/CodexSkillReferenceContractTest.php
diff --git a/tests/Architecture/AgentSystemCatalogContractTest.php b/tests/Architecture/AgentSystemCatalogContractTest.php
new file mode 100644
index 0000000..1c4932d
--- /dev/null
+++ b/tests/Architecture/AgentSystemCatalogContractTest.php
@@ -0,0 +1,64 @@
+decodeAgentJson('.agents/checks/guard-catalog.json');
+ $guards = is_array($catalog['guards'] ?? null) ? $catalog['guards'] : [];
+ $this->assertNotSame([], $guards, 'Guard catalog must not be empty.');
+
+ $ids = [];
+ foreach ($guards as $guard) {
+ $id = (string) ($guard['id'] ?? '');
+ $reviewer = (string) ($guard['reviewer'] ?? '');
+ $this->assertMatchesRegularExpression('/^GR-(CORE|TEST|UI|SEC)-[A-Z0-9]{3,}$/', $id, 'Invalid guard id: ' . $id);
+ $this->assertContains($reviewer, ['code', 'security'], 'Invalid reviewer for ' . $id);
+ $ids[] = $id;
+ }
+
+ $this->assertSame($ids, array_values(array_unique($ids)), 'Duplicate guard IDs found.');
+ }
+
+ public function testGuardCatalogReviewerAssignment(): void
+ {
+ $catalog = $this->decodeAgentJson('.agents/checks/guard-catalog.json');
+ $guards = is_array($catalog['guards'] ?? null) ? $catalog['guards'] : [];
+
+ foreach ($guards as $guard) {
+ $id = (string) ($guard['id'] ?? '');
+ $reviewer = (string) ($guard['reviewer'] ?? '');
+
+ if (str_starts_with($id, 'GR-SEC-')) {
+ $this->assertSame('security', $reviewer, $id . ' must be assigned to security reviewer');
+ } else {
+ $this->assertSame('code', $reviewer, $id . ' must be assigned to code reviewer');
+ }
+ }
+ }
+
+ public function testQualityGateIdsAreUniqueAndWellFormed(): void
+ {
+ $gates = $this->decodeAgentJson('.agents/checks/quality-gates.json');
+ $items = is_array($gates['gates'] ?? null) ? $gates['gates'] : [];
+ $this->assertNotSame([], $items, 'Quality gates must not be empty.');
+
+ $ids = [];
+ foreach ($items as $gate) {
+ $id = (string) ($gate['id'] ?? '');
+ $command = (string) ($gate['command'] ?? '');
+ $this->assertMatchesRegularExpression('/^QG-[0-9]{3}$/', $id, 'Invalid quality gate id: ' . $id);
+ $this->assertNotSame('', trim($command), 'Missing command for ' . $id);
+ $ids[] = $id;
+ }
+
+ $this->assertSame($ids, array_values(array_unique($ids)), 'Duplicate quality gate IDs found.');
+ }
+}
diff --git a/tests/Architecture/AgentSystemContractSupport.php b/tests/Architecture/AgentSystemContractSupport.php
new file mode 100644
index 0000000..dd4dbba
--- /dev/null
+++ b/tests/Architecture/AgentSystemContractSupport.php
@@ -0,0 +1,16 @@
+
+ */
+ private function decodeAgentJson(string $path): array
+ {
+ $decoded = json_decode($this->readProjectFile($path), true);
+ $this->assertIsArray($decoded, 'Invalid JSON in ' . $path);
+ return $decoded;
+ }
+}
diff --git a/tests/Architecture/AgentSystemContractTest.php b/tests/Architecture/AgentSystemContractTest.php
deleted file mode 100644
index a180200..0000000
--- a/tests/Architecture/AgentSystemContractTest.php
+++ /dev/null
@@ -1,198 +0,0 @@
-readProjectFile($file), true);
- $this->assertIsArray($decoded, 'Invalid JSON: ' . $file);
- }
- }
-
- public function testGuardCatalogIdsAreUniqueAndWellFormed(): void
- {
- $catalog = $this->decodeJson('.agents/checks/guard-catalog.json');
- $guards = is_array($catalog['guards'] ?? null) ? $catalog['guards'] : [];
- $this->assertNotSame([], $guards, 'Guard catalog must not be empty.');
-
- $ids = [];
- foreach ($guards as $guard) {
- $id = (string) ($guard['id'] ?? '');
- $reviewer = (string) ($guard['reviewer'] ?? '');
- $this->assertMatchesRegularExpression('/^GR-(CORE|TEST|UI|SEC)-[A-Z0-9]{3,}$/', $id, 'Invalid guard id: ' . $id);
- $this->assertContains($reviewer, ['code', 'security'], 'Invalid reviewer for ' . $id);
- $ids[] = $id;
- }
-
- $this->assertSame($ids, array_values(array_unique($ids)), 'Duplicate guard IDs found.');
- }
-
- public function testGuardCatalogReviewerAssignment(): void
- {
- $catalog = $this->decodeJson('.agents/checks/guard-catalog.json');
- $guards = is_array($catalog['guards'] ?? null) ? $catalog['guards'] : [];
-
- foreach ($guards as $guard) {
- $id = (string) ($guard['id'] ?? '');
- $reviewer = (string) ($guard['reviewer'] ?? '');
-
- if (str_starts_with($id, 'GR-SEC-')) {
- $this->assertSame('security', $reviewer, $id . ' must be assigned to security reviewer');
- } else {
- $this->assertSame('code', $reviewer, $id . ' must be assigned to code reviewer');
- }
- }
- }
-
- public function testQualityGateIdsAreUniqueAndWellFormed(): void
- {
- $gates = $this->decodeJson('.agents/checks/quality-gates.json');
- $items = is_array($gates['gates'] ?? null) ? $gates['gates'] : [];
- $this->assertNotSame([], $items, 'Quality gates must not be empty.');
-
- $ids = [];
- foreach ($items as $gate) {
- $id = (string) ($gate['id'] ?? '');
- $command = (string) ($gate['command'] ?? '');
- $this->assertMatchesRegularExpression('/^QG-[0-9]{3}$/', $id, 'Invalid quality gate id: ' . $id);
- $this->assertNotSame('', trim($command), 'Missing command for ' . $id);
- $ids[] = $id;
- }
-
- $this->assertSame($ids, array_values(array_unique($ids)), 'Duplicate quality gate IDs found.');
- }
-
- public function testTemplatesReferenceKnownGuardAndGateIds(): void
- {
- $catalog = $this->decodeJson('.agents/checks/guard-catalog.json');
- $gates = $this->decodeJson('.agents/checks/quality-gates.json');
- $guardIds = array_map(
- static fn (array $guard): string => (string) ($guard['id'] ?? ''),
- is_array($catalog['guards'] ?? null) ? $catalog['guards'] : []
- );
- $gateIds = array_map(
- static fn (array $gate): string => (string) ($gate['id'] ?? ''),
- is_array($gates['gates'] ?? null) ? $gates['gates'] : []
- );
-
- $planTemplate = $this->decodeJson('.agents/templates/plan.template.json');
- $planGuardIds = is_array($planTemplate['guardrails']['required_guard_ids'] ?? null)
- ? $planTemplate['guardrails']['required_guard_ids']
- : [];
- $planGateIds = is_array($planTemplate['guardrails']['required_quality_gate_ids'] ?? null)
- ? $planTemplate['guardrails']['required_quality_gate_ids']
- : [];
- foreach ($planGuardIds as $id) {
- $this->assertContains($id, $guardIds, 'Unknown guard id in plan template: ' . (string) $id);
- }
- foreach ($planGateIds as $id) {
- $this->assertContains($id, $gateIds, 'Unknown gate id in plan template: ' . (string) $id);
- }
-
- $reviewCodeTemplate = $this->decodeJson('.agents/templates/review-code.template.json');
- $reviewGuardIds = is_array($reviewCodeTemplate['checked_guard_ids'] ?? null)
- ? $reviewCodeTemplate['checked_guard_ids']
- : [];
- $reviewGateIds = is_array($reviewCodeTemplate['checked_quality_gate_ids'] ?? null)
- ? $reviewCodeTemplate['checked_quality_gate_ids']
- : [];
- foreach ($reviewGuardIds as $id) {
- $this->assertContains($id, $guardIds, 'Unknown guard id in review-code template: ' . (string) $id);
- }
- foreach ($reviewGateIds as $id) {
- $this->assertContains($id, $gateIds, 'Unknown gate id in review-code template: ' . (string) $id);
- }
-
- $reviewSecurityTemplate = $this->decodeJson('.agents/templates/review-security.template.json');
- $secGuardIds = is_array($reviewSecurityTemplate['checked_guard_ids'] ?? null)
- ? $reviewSecurityTemplate['checked_guard_ids']
- : [];
- foreach ($secGuardIds as $id) {
- $this->assertContains($id, $guardIds, 'Unknown guard id in review-security template: ' . (string) $id);
- }
- }
-
- public function testRolePromptsReferenceGuardCatalog(): void
- {
- $plannerPrompt = $this->readProjectFile('.agents/prompts/planner.md');
- $executorPrompt = $this->readProjectFile('.agents/prompts/executor.md');
- $codeReviewerPrompt = $this->readProjectFile('.agents/prompts/reviewer-code.md');
- $securityReviewerPrompt = $this->readProjectFile('.agents/prompts/reviewer-security.md');
-
- $this->assertStringContainsString('.agents/checks/guard-catalog.json', $plannerPrompt);
- $this->assertStringContainsString('.agents/checks/quality-gates.json', $plannerPrompt);
- $this->assertStringContainsString('.agents/checks/guard-catalog.json', $executorPrompt);
- $this->assertStringContainsString('.agents/checks/quality-gates.json', $executorPrompt);
- $this->assertStringContainsString('.agents/checks/guard-catalog.json', $codeReviewerPrompt);
- $this->assertStringContainsString('.agents/checks/guard-catalog.json', $securityReviewerPrompt);
- }
-
- public function testAllSevenRolePromptsExist(): void
- {
- $roles = ['analyst', 'planner', 'executor', 'reviewer-code', 'reviewer-security', 'reviewer-acceptance', 'finalizer'];
-
- foreach ($roles as $role) {
- $this->readProjectFile('.agents/prompts/' . $role . '.md');
- }
- }
-
- public function testAllSevenContractsExist(): void
- {
- $contracts = ['analyst', 'planner', 'executor', 'reviewer-code', 'reviewer-security', 'reviewer-acceptance', 'finalizer'];
-
- foreach ($contracts as $contract) {
- $decoded = json_decode($this->readProjectFile('.agents/contracts/' . $contract . '.schema.json'), true);
- $this->assertIsArray($decoded, 'Invalid JSON schema: ' . $contract);
- }
- }
-
- public function testAllSevenTemplatesExist(): void
- {
- $templates = [
- 'analysis', 'plan', 'execution-report',
- 'review-code', 'review-security', 'review-acceptance', 'finalize',
- ];
-
- foreach ($templates as $template) {
- $decoded = json_decode($this->readProjectFile('.agents/templates/' . $template . '.template.json'), true);
- $this->assertIsArray($decoded, 'Invalid JSON template: ' . $template);
- }
- }
-
- /**
- * @return array
- */
- private function decodeJson(string $path): array
- {
- $decoded = json_decode($this->readProjectFile($path), true);
- $this->assertIsArray($decoded, 'Invalid JSON in ' . $path);
- return $decoded;
- }
-}
diff --git a/tests/Architecture/AgentSystemFileContractTest.php b/tests/Architecture/AgentSystemFileContractTest.php
new file mode 100644
index 0000000..6bb5409
--- /dev/null
+++ b/tests/Architecture/AgentSystemFileContractTest.php
@@ -0,0 +1,70 @@
+readProjectFile($file), true);
+ $this->assertIsArray($decoded, 'Invalid JSON: ' . $file);
+ }
+ }
+
+ public function testAllSevenRolePromptsExist(): void
+ {
+ $roles = ['analyst', 'planner', 'executor', 'reviewer-code', 'reviewer-security', 'reviewer-acceptance', 'finalizer'];
+
+ foreach ($roles as $role) {
+ $this->readProjectFile('.agents/prompts/' . $role . '.md');
+ }
+ }
+
+ public function testAllSevenContractsExist(): void
+ {
+ $contracts = ['analyst', 'planner', 'executor', 'reviewer-code', 'reviewer-security', 'reviewer-acceptance', 'finalizer'];
+
+ foreach ($contracts as $contract) {
+ $decoded = json_decode($this->readProjectFile('.agents/contracts/' . $contract . '.schema.json'), true);
+ $this->assertIsArray($decoded, 'Invalid JSON schema: ' . $contract);
+ }
+ }
+
+ public function testAllSevenTemplatesExist(): void
+ {
+ $templates = [
+ 'analysis', 'plan', 'execution-report',
+ 'review-code', 'review-security', 'review-acceptance', 'finalize',
+ ];
+
+ foreach ($templates as $template) {
+ $decoded = json_decode($this->readProjectFile('.agents/templates/' . $template . '.template.json'), true);
+ $this->assertIsArray($decoded, 'Invalid JSON template: ' . $template);
+ }
+ }
+}
diff --git a/tests/Architecture/AgentSystemReferenceContractTest.php b/tests/Architecture/AgentSystemReferenceContractTest.php
new file mode 100644
index 0000000..49dd44c
--- /dev/null
+++ b/tests/Architecture/AgentSystemReferenceContractTest.php
@@ -0,0 +1,76 @@
+decodeAgentJson('.agents/checks/guard-catalog.json');
+ $gates = $this->decodeAgentJson('.agents/checks/quality-gates.json');
+ $guardIds = array_map(
+ static fn (array $guard): string => (string) ($guard['id'] ?? ''),
+ is_array($catalog['guards'] ?? null) ? $catalog['guards'] : []
+ );
+ $gateIds = array_map(
+ static fn (array $gate): string => (string) ($gate['id'] ?? ''),
+ is_array($gates['gates'] ?? null) ? $gates['gates'] : []
+ );
+
+ $planTemplate = $this->decodeAgentJson('.agents/templates/plan.template.json');
+ $planGuardIds = is_array($planTemplate['guardrails']['required_guard_ids'] ?? null)
+ ? $planTemplate['guardrails']['required_guard_ids']
+ : [];
+ $planGateIds = is_array($planTemplate['guardrails']['required_quality_gate_ids'] ?? null)
+ ? $planTemplate['guardrails']['required_quality_gate_ids']
+ : [];
+ foreach ($planGuardIds as $id) {
+ $this->assertContains($id, $guardIds, 'Unknown guard id in plan template: ' . (string) $id);
+ }
+ foreach ($planGateIds as $id) {
+ $this->assertContains($id, $gateIds, 'Unknown gate id in plan template: ' . (string) $id);
+ }
+
+ $reviewCodeTemplate = $this->decodeAgentJson('.agents/templates/review-code.template.json');
+ $reviewGuardIds = is_array($reviewCodeTemplate['checked_guard_ids'] ?? null)
+ ? $reviewCodeTemplate['checked_guard_ids']
+ : [];
+ $reviewGateIds = is_array($reviewCodeTemplate['checked_quality_gate_ids'] ?? null)
+ ? $reviewCodeTemplate['checked_quality_gate_ids']
+ : [];
+ foreach ($reviewGuardIds as $id) {
+ $this->assertContains($id, $guardIds, 'Unknown guard id in review-code template: ' . (string) $id);
+ }
+ foreach ($reviewGateIds as $id) {
+ $this->assertContains($id, $gateIds, 'Unknown gate id in review-code template: ' . (string) $id);
+ }
+
+ $reviewSecurityTemplate = $this->decodeAgentJson('.agents/templates/review-security.template.json');
+ $secGuardIds = is_array($reviewSecurityTemplate['checked_guard_ids'] ?? null)
+ ? $reviewSecurityTemplate['checked_guard_ids']
+ : [];
+ foreach ($secGuardIds as $id) {
+ $this->assertContains($id, $guardIds, 'Unknown guard id in review-security template: ' . (string) $id);
+ }
+ }
+
+ public function testRolePromptsReferenceGuardCatalog(): void
+ {
+ $plannerPrompt = $this->readProjectFile('.agents/prompts/planner.md');
+ $executorPrompt = $this->readProjectFile('.agents/prompts/executor.md');
+ $codeReviewerPrompt = $this->readProjectFile('.agents/prompts/reviewer-code.md');
+ $securityReviewerPrompt = $this->readProjectFile('.agents/prompts/reviewer-security.md');
+
+ $this->assertStringContainsString('.agents/checks/guard-catalog.json', $plannerPrompt);
+ $this->assertStringContainsString('.agents/checks/quality-gates.json', $plannerPrompt);
+ $this->assertStringContainsString('.agents/checks/guard-catalog.json', $executorPrompt);
+ $this->assertStringContainsString('.agents/checks/quality-gates.json', $executorPrompt);
+ $this->assertStringContainsString('.agents/checks/guard-catalog.json', $codeReviewerPrompt);
+ $this->assertStringContainsString('.agents/checks/guard-catalog.json', $securityReviewerPrompt);
+ }
+}
diff --git a/tests/Architecture/CodexAutomationContractTest.php b/tests/Architecture/CodexAutomationContractTest.php
new file mode 100644
index 0000000..258c538
--- /dev/null
+++ b/tests/Architecture/CodexAutomationContractTest.php
@@ -0,0 +1,46 @@
+readProjectFile('bin/codex-skills-sync.sh');
+ $this->assertStringContainsString('--check', $script);
+ $this->assertStringContainsString('--apply', $script);
+ $this->assertStringContainsString('mode="check"', $script);
+ $this->assertStringContainsString('"core-guardrails"', $script);
+ $this->assertStringContainsString('"starterkit-planner"', $script);
+ $this->assertStringContainsString('"starterkit-grid-standards"', $script);
+ $this->assertStringContainsString('"starterkit-php-style-ci"', $script);
+ }
+
+ public function testDocsLinkCheckScriptExcludesHistoricalRunArtifactsByDefault(): void
+ {
+ $script = $this->readProjectFile('bin/docs-link-check.sh');
+ $this->assertStringContainsString("!.agents/runs/**", $script);
+ }
+
+ public function testQualityGatesIncludeDocsLinksAndSkillsSyncChecks(): void
+ {
+ $qualityGates = json_decode($this->readProjectFile('.agents/checks/quality-gates.json'), true);
+ $this->assertIsArray($qualityGates, 'Invalid quality-gates.json');
+ $gates = is_array($qualityGates['gates'] ?? null) ? $qualityGates['gates'] : [];
+
+ $byId = [];
+ foreach ($gates as $gate) {
+ $id = (string) ($gate['id'] ?? '');
+ $byId[$id] = $gate;
+ }
+
+ $this->assertArrayHasKey('QG-008', $byId);
+ $this->assertArrayHasKey('QG-009', $byId);
+ $this->assertSame('bin/docs-link-check.sh', (string) ($byId['QG-008']['command'] ?? ''));
+ $this->assertSame('bin/codex-skills-sync.sh --check', (string) ($byId['QG-009']['command'] ?? ''));
+ }
+}
diff --git a/tests/Architecture/CodexDocsContractSupport.php b/tests/Architecture/CodexDocsContractSupport.php
new file mode 100644
index 0000000..fa49d7a
--- /dev/null
+++ b/tests/Architecture/CodexDocsContractSupport.php
@@ -0,0 +1,46 @@
+
+ */
+ private function filesCoveredByDocsLinkIntegrityCheck(): array
+ {
+ $root = dirname(__DIR__, 2);
+ $files = ['README.md'];
+ $scanDirs = ['docs', '.agents'];
+
+ foreach ($scanDirs as $scanDir) {
+ $directory = new \RecursiveDirectoryIterator(
+ $root . '/' . $scanDir,
+ \FilesystemIterator::SKIP_DOTS
+ );
+ $iterator = new \RecursiveIteratorIterator($directory);
+
+ foreach ($iterator as $fileInfo) {
+ if (!$fileInfo->isFile()) {
+ continue;
+ }
+
+ $fullPath = str_replace('\\', '/', $fileInfo->getPathname());
+ if (!str_ends_with($fullPath, '.md')) {
+ continue;
+ }
+
+ $relative = ltrim(str_replace(str_replace('\\', '/', $root), '', $fullPath), '/');
+ if (str_starts_with($relative, '.agents/runs/')) {
+ continue;
+ }
+
+ $files[] = $relative;
+ }
+ }
+
+ $files = array_values(array_unique($files));
+ sort($files);
+ return $files;
+ }
+}
diff --git a/tests/Architecture/CodexDocsIntegrityContractTest.php b/tests/Architecture/CodexDocsIntegrityContractTest.php
new file mode 100644
index 0000000..27df9e1
--- /dev/null
+++ b/tests/Architecture/CodexDocsIntegrityContractTest.php
@@ -0,0 +1,75 @@
+readProjectFile('docs/reference-codex-prompts.md');
+ $this->assertStringContainsString('core-guardrails', $prompts);
+ $this->assertStringContainsString('starterkit-planner', $prompts);
+ }
+
+ public function testDocsIndexReferencesCodexPrompts(): void
+ {
+ $index = $this->readProjectFile('docs/index.md');
+ $this->assertStringContainsString('/docs/reference-codex-prompts.md', $index);
+ }
+
+ public function testDocumentationLinksResolveAcrossDocsSkillsAndAgentGuides(): void
+ {
+ foreach ($this->filesCoveredByDocsLinkIntegrityCheck() as $file) {
+ $content = $this->readProjectFile($file);
+ preg_match_all('/(?assertFileExists($absolute, sprintf('Broken docs link "%s" in %s', $link, $file));
+ }
+ }
+ }
+
+ public function testDocsIndexCoversAllMarkdownDocsWithoutOrphansOrDuplicates(): void
+ {
+ $index = $this->readProjectFile('docs/index.md');
+ preg_match_all('/\\/docs\\/([a-z0-9-]+\\.md)\\b/', $index, $matches);
+ $references = $matches[1];
+
+ $docFiles = glob(dirname(__DIR__, 2) . '/docs/*.md');
+ $this->assertNotFalse($docFiles, 'Failed to enumerate docs/*.md files.');
+
+ $expectedDocs = [];
+ foreach ($docFiles as $fullPath) {
+ $basename = basename($fullPath);
+ if ($basename !== 'index.md') {
+ $expectedDocs[] = $basename;
+ }
+ }
+
+ $refCounts = array_count_values($references);
+ $duplicates = [];
+ foreach ($refCounts as $ref => $count) {
+ if ($count > 1) {
+ $duplicates[] = $ref;
+ }
+ }
+
+ $uniqueReferences = array_values(array_unique($references));
+ sort($expectedDocs);
+ sort($uniqueReferences);
+ sort($duplicates);
+
+ $orphans = array_values(array_diff($expectedDocs, $uniqueReferences));
+ $missingTargets = array_values(array_diff($uniqueReferences, $expectedDocs));
+
+ $this->assertSame([], $duplicates, 'Duplicate docs/index.md references: ' . implode(', ', $duplicates));
+ $this->assertSame([], $orphans, 'Docs markdown files missing in docs/index.md: ' . implode(', ', $orphans));
+ $this->assertSame([], $missingTargets, 'docs/index.md references missing files: ' . implode(', ', $missingTargets));
+ }
+}
diff --git a/tests/Architecture/CodexSkillReferenceContractTest.php b/tests/Architecture/CodexSkillReferenceContractTest.php
new file mode 100644
index 0000000..1651b23
--- /dev/null
+++ b/tests/Architecture/CodexSkillReferenceContractTest.php
@@ -0,0 +1,46 @@
+readProjectFile('.agents/skills/core-guardrails/SKILL.md');
+ $this->assertStringContainsString('name: core-guardrails', $skill);
+
+ $this->readProjectFile('.agents/skills/core-guardrails/references/boundaries-core.md');
+ $this->readProjectFile('.agents/skills/core-guardrails/references/boundaries-ui.md');
+ $this->readProjectFile('.agents/skills/core-guardrails/references/quality-gates.md');
+ $this->readProjectFile('.agents/skills/core-guardrails/references/source-map.md');
+ }
+
+ public function testStarterkitPlannerSkillAndReferencesExist(): void
+ {
+ $skill = $this->readProjectFile('.agents/skills/starterkit-planner/SKILL.md');
+ $this->assertStringContainsString('name: starterkit-planner', $skill);
+
+ $this->readProjectFile('.agents/skills/starterkit-planner/references/plan-template.md');
+ $this->readProjectFile('.agents/skills/starterkit-planner/references/tradeoff-matrix.md');
+ }
+
+ public function testStarterkitGridStandardsSkillAndReferencesExist(): void
+ {
+ $skill = $this->readProjectFile('.agents/skills/starterkit-grid-standards/SKILL.md');
+ $this->assertStringContainsString('name: starterkit-grid-standards', $skill);
+
+ $this->readProjectFile('.agents/skills/starterkit-grid-standards/references/list-checklist.md');
+ $this->readProjectFile('.agents/skills/starterkit-grid-standards/references/row-interaction-standard.md');
+ $this->readProjectFile('.agents/skills/starterkit-grid-standards/references/page-size-standard.md');
+ }
+
+ public function testStarterkitPhpStyleCiSkillExists(): void
+ {
+ $skill = $this->readProjectFile('.agents/skills/starterkit-php-style-ci/SKILL.md');
+ $this->assertStringContainsString('name: starterkit-php-style-ci', $skill);
+ }
+}
diff --git a/tests/Architecture/CodexSkillsContractTest.php b/tests/Architecture/CodexSkillsContractTest.php
deleted file mode 100644
index b577c66..0000000
--- a/tests/Architecture/CodexSkillsContractTest.php
+++ /dev/null
@@ -1,209 +0,0 @@
-readProjectFile('tools/codex-skills/core-guardrails/SKILL.md');
- $this->assertStringContainsString('name: core-guardrails', $skill);
-
- $this->readProjectFile('tools/codex-skills/core-guardrails/references/boundaries-core.md');
- $this->readProjectFile('tools/codex-skills/core-guardrails/references/boundaries-ui.md');
- $this->readProjectFile('tools/codex-skills/core-guardrails/references/quality-gates.md');
- $this->readProjectFile('tools/codex-skills/core-guardrails/references/source-map.md');
- }
-
- public function testStarterkitPlannerSkillAndReferencesExist(): void
- {
- $skill = $this->readProjectFile('tools/codex-skills/starterkit-planner/SKILL.md');
- $this->assertStringContainsString('name: starterkit-planner', $skill);
-
- $this->readProjectFile('tools/codex-skills/starterkit-planner/references/plan-template.md');
- $this->readProjectFile('tools/codex-skills/starterkit-planner/references/tradeoff-matrix.md');
- }
-
- public function testStarterkitGridStandardsSkillAndReferencesExist(): void
- {
- $skill = $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/SKILL.md');
- $this->assertStringContainsString('name: starterkit-grid-standards', $skill);
-
- $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/references/list-checklist.md');
- $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/references/row-interaction-standard.md');
- $this->readProjectFile('tools/codex-skills/starterkit-grid-standards/references/page-size-standard.md');
- }
-
- public function testStarterkitPhpStyleCiSkillExists(): void
- {
- $skill = $this->readProjectFile('tools/codex-skills/starterkit-php-style-ci/SKILL.md');
- $this->assertStringContainsString('name: starterkit-php-style-ci', $skill);
- }
-
- public function testCodexPromptsDocumentationReferencesBothSkills(): void
- {
- $prompts = $this->readProjectFile('docs/reference-codex-prompts.md');
- $this->assertStringContainsString('core-guardrails', $prompts);
- $this->assertStringContainsString('starterkit-planner', $prompts);
- }
-
- public function testDocsIndexReferencesCodexPrompts(): void
- {
- $index = $this->readProjectFile('docs/index.md');
- $this->assertStringContainsString('/docs/reference-codex-prompts.md', $index);
- }
-
- public function testCodexSkillsSyncScriptSupportsCheckAndApply(): void
- {
- $script = $this->readProjectFile('bin/codex-skills-sync.sh');
- $this->assertStringContainsString('--check', $script);
- $this->assertStringContainsString('--apply', $script);
- $this->assertStringContainsString('mode="check"', $script);
- $this->assertStringContainsString('"core-guardrails"', $script);
- $this->assertStringContainsString('"starterkit-planner"', $script);
- $this->assertStringContainsString('"starterkit-grid-standards"', $script);
- $this->assertStringContainsString('"starterkit-php-style-ci"', $script);
- }
-
- public function testDocsLinkCheckScriptExcludesHistoricalRunArtifactsByDefault(): void
- {
- $script = $this->readProjectFile('bin/docs-link-check.sh');
- $this->assertStringContainsString("!agent-system/runs/**", $script);
- }
-
- public function testDocumentationLinksResolveAcrossDocsSkillsAndAgentGuides(): void
- {
- foreach ($this->filesCoveredByDocsLinkIntegrityCheck() as $file) {
- $content = $this->readProjectFile($file);
- preg_match_all('/(?projectRootPath() . '/' . $normalized;
- $this->assertFileExists($absolute, sprintf('Broken docs link "%s" in %s', $link, $file));
- }
- }
- }
-
- public function testDocsIndexCoversAllMarkdownDocsWithoutOrphansOrDuplicates(): void
- {
- $index = $this->readProjectFile('docs/index.md');
- preg_match_all('/\\/docs\\/([a-z0-9-]+\\.md)\\b/', $index, $matches);
- $references = $matches[1];
-
- $docFiles = glob($this->projectRootPath() . '/docs/*.md');
- $this->assertNotFalse($docFiles, 'Failed to enumerate docs/*.md files.');
-
- $expectedDocs = [];
- foreach ($docFiles as $fullPath) {
- $basename = basename($fullPath);
- if ($basename !== 'index.md') {
- $expectedDocs[] = $basename;
- }
- }
-
- $refCounts = array_count_values($references);
- $duplicates = [];
- foreach ($refCounts as $ref => $count) {
- if ($count > 1) {
- $duplicates[] = $ref;
- }
- }
-
- $uniqueReferences = array_values(array_unique($references));
- sort($expectedDocs);
- sort($uniqueReferences);
- sort($duplicates);
-
- $orphans = array_values(array_diff($expectedDocs, $uniqueReferences));
- $missingTargets = array_values(array_diff($uniqueReferences, $expectedDocs));
-
- $this->assertSame([], $duplicates, 'Duplicate docs/index.md references: ' . implode(', ', $duplicates));
- $this->assertSame([], $orphans, 'Docs markdown files missing in docs/index.md: ' . implode(', ', $orphans));
- $this->assertSame([], $missingTargets, 'docs/index.md references missing files: ' . implode(', ', $missingTargets));
- }
-
- public function testQualityGatesIncludeDocsLinksAndSkillsSyncChecks(): void
- {
- $qualityGates = json_decode($this->readProjectFile('agent-system/checks/quality-gates.json'), true);
- $this->assertIsArray($qualityGates, 'Invalid quality-gates.json');
- $gates = is_array($qualityGates['gates'] ?? null) ? $qualityGates['gates'] : [];
-
- $byId = [];
- foreach ($gates as $gate) {
- $id = (string) ($gate['id'] ?? '');
- $byId[$id] = $gate;
- }
-
- $this->assertArrayHasKey('QG-008', $byId);
- $this->assertArrayHasKey('QG-009', $byId);
- $this->assertSame('bin/docs-link-check.sh', (string) ($byId['QG-008']['command'] ?? ''));
- $this->assertSame('bin/codex-skills-sync.sh --check', (string) ($byId['QG-009']['command'] ?? ''));
- }
-
- public function testDocsSkillsAuditPolicyExists(): void
- {
- $policy = $this->readProjectFile('agent-system/checks/docs-skills-audit-policy.md');
- $this->assertStringContainsString('P0', $policy);
- $this->assertStringContainsString('P1', $policy);
- $this->assertStringContainsString('DOCS-SKILLS-AUDIT-00x', $policy);
- }
-
- public function testReviewerGuardsFlowEnforcesDocsSkillsAuditPolicy(): void
- {
- $prompt = $this->readProjectFile('agent-system/prompts/reviewer-guards.md');
- $schema = $this->readProjectFile('agent-system/contracts/reviewer-guards.schema.json');
-
- $this->assertStringContainsString('agent-system/checks/docs-skills-audit-policy.md', $prompt);
- $this->assertStringContainsString('DOCS-SKILLS-AUDIT', $prompt);
-
- $this->assertStringContainsString('audit_policy_ref', $schema);
- $this->assertStringContainsString('"P0"', $schema);
- $this->assertStringContainsString('"P1"', $schema);
- $this->assertStringContainsString('run_artifact_id', $schema);
- $this->assertStringContainsString('DOCS-SKILLS-AUDIT-[0-9]{3}', $schema);
- }
-
- /**
- * @return list
- */
- private function filesCoveredByDocsLinkIntegrityCheck(): array
- {
- $root = $this->projectRootPath();
- $files = ['README.md'];
- $scanDirs = ['docs', 'tools/codex-skills', 'agent-system'];
-
- foreach ($scanDirs as $scanDir) {
- $directory = new \RecursiveDirectoryIterator(
- $root . '/' . $scanDir,
- \FilesystemIterator::SKIP_DOTS
- );
- $iterator = new \RecursiveIteratorIterator($directory);
-
- foreach ($iterator as $fileInfo) {
- if (!$fileInfo->isFile()) {
- continue;
- }
-
- $fullPath = str_replace('\\', '/', $fileInfo->getPathname());
- if (!str_ends_with($fullPath, '.md')) {
- continue;
- }
-
- $relative = ltrim(str_replace(str_replace('\\', '/', $root), '', $fullPath), '/');
- if (str_starts_with($relative, 'agent-system/runs/')) {
- continue;
- }
-
- $files[] = $relative;
- }
- }
-
- $files = array_values(array_unique($files));
- sort($files);
- return $files;
- }
-}
diff --git a/tests/README.md b/tests/README.md
index 53dc58d..a2c0d1a 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -32,8 +32,8 @@ ohne dass jede Aenderung immer die komplette Suite im Kopf behalten muss.
## Naechste Refactor-Kandidaten
-- `tests/Architecture/CodexSkillsContractTest.php`
- `tests/Architecture/ModuleRegistryContractTest.php`
-- `tests/Architecture/AgentSystemContractTest.php`
- `tests/Architecture/DetailValidationSummaryContractTest.php`
- `tests/Architecture/DetailActionPolicyContractTest.php`
+- `tests/Architecture/NoAddressBookHardcodingTest.php`
+- `tests/Architecture/NoBookmarksHardcodingTest.php`