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; } }