Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
<?php
namespace App\RSpade\CodeQuality\Rules\Jqhtml;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
use App\RSpade\Core\Manifest\Manifest;
/**
* JQHTML Component Naming Rule
*
* Enforces that all jqhtml component names start with an uppercase letter.
* This is a hard requirement of the jqhtml library.
*/
class JqhtmlComponentNaming_CodeQualityRule extends CodeQualityRule_Abstract
{
/**
* Get the unique identifier for this rule
*/
public function get_id(): string
{
return 'JQHTML-NAMING-01';
}
/**
* Get the human-readable name of this rule
*/
public function get_name(): string
{
return 'JQHTML Component Names Must Start Uppercase';
}
/**
* Get the description of what this rule checks
*/
public function get_description(): string
{
return 'Ensures all jqhtml component names start with an uppercase letter (library requirement)';
}
/**
* Get file patterns this rule should check
*/
public function get_file_patterns(): array
{
return ['*.jqhtml', '*.js'];
}
/**
* This rule should run at manifest-time for immediate feedback
* since incorrect naming would break the jqhtml library
*/
public function is_called_during_manifest_scan(): bool
{
return true; // Critical library requirement
}
/**
* Check the file for violations
*/
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Check .jqhtml files for Define: tags
if (str_ends_with($file_path, '.jqhtml')) {
$this->check_jqhtml_file($file_path, $contents);
}
// Check .js files for classes extending Jqhtml_Component
if (str_ends_with($file_path, '.js')) {
$this->check_javascript_file($file_path, $contents, $metadata);
}
}
/**
* Check jqhtml template files
*/
private function check_jqhtml_file(string $file_path, string $contents): void
{
$lines = explode("\n", $contents);
$line_number = 0;
foreach ($lines as $line) {
$line_number++;
// Look for <Define:ComponentName> tags
if (preg_match('/<Define:([a-zA-Z_][a-zA-Z0-9_]*)>/', $line, $matches)) {
$component_name = $matches[1];
// Check if first character is not uppercase
if (!ctype_upper($component_name[0])) {
$this->add_violation(
$file_path,
$line_number,
"JQHTML component name '{$component_name}' must start with an uppercase letter",
trim($line),
"Change '{$component_name}' to '" . ucfirst($component_name) . "'. This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.",
'critical'
);
}
}
}
}
/**
* Check JavaScript files for Jqhtml_Component subclasses
*/
private function check_javascript_file(string $file_path, string $contents, array $metadata = []): void
{
$lines = explode("\n", $contents);
$line_number = 0;
// Get JavaScript class from manifest metadata
$js_classes = [];
if (isset($metadata['class']) && isset($metadata['extension']) && $metadata['extension'] === 'js') {
$js_classes = [$metadata['class']];
}
// If no classes in metadata, nothing to check for class definitions
if (!empty($js_classes)) {
// Find line numbers for each class
$class_definitions = [];
foreach ($js_classes as $class_name) {
// Find where this class is defined in the source
foreach ($lines as $idx => $line) {
if (preg_match('/class\s+' . preg_quote($class_name, '/') . '\s+/', $line)) {
$class_definitions[$class_name] = $idx + 1;
break;
}
}
}
// Check each class to see if it's a JQHTML component
foreach ($class_definitions as $class_name => $line_num) {
// Use Manifest to check if this is a JQHTML component (handles indirect inheritance)
if (Manifest::js_is_subclass_of($class_name, 'Jqhtml_Component')) {
// Check if first character is not uppercase
if (!ctype_upper($class_name[0])) {
$this->add_violation(
$file_path,
$line_num,
"JQHTML component class '{$class_name}' must start with an uppercase letter",
trim($lines[$line_num - 1]),
"Change '{$class_name}' to '" . ucfirst($class_name) . "'. This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.",
'critical'
);
}
}
}
}
// Still check for component registration patterns
foreach ($lines as $line) {
$line_number++;
// Also check for component registration patterns
if (preg_match('/jqhtml\.component\([\'"]([a-zA-Z_][a-zA-Z0-9_]*)[\'"]/', $line, $matches)) {
$component_name = $matches[1];
if (!ctype_upper($component_name[0])) {
$this->add_violation(
$file_path,
$line_number,
"JQHTML component registration '{$component_name}' must use uppercase name",
trim($line),
"Change '{$component_name}' to '" . ucfirst($component_name) . "'. This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.",
'critical'
);
}
}
}
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace App\RSpade\CodeQuality\Rules\Jqhtml;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
use App\RSpade\Core\Manifest\Manifest;
/**
* Rule: JQHTML-EVENT-01
*
* Detects incorrect usage of event.preventDefault() in JQHTML component event handlers.
* JQHTML @event attributes don't pass DOM event objects - they call methods directly
* and preventDefault is handled automatically by the framework.
*/
class JqhtmlEventPreventDefault_CodeQualityRule extends CodeQualityRule_Abstract
{
public function get_id(): string
{
return 'JQHTML-EVENT-01';
}
public function get_name(): string
{
return 'JQHTML Event preventDefault Usage';
}
public function get_description(): string
{
return 'Detects incorrect usage of event.preventDefault() in JQHTML component event handlers. ' .
"JQHTML @event attributes don't pass DOM event objects - they call methods directly " .
'and preventDefault is handled automatically by the framework.';
}
public function get_file_patterns(): array
{
return ['*.js'];
}
public function get_default_severity(): string
{
return 'high';
}
public function is_called_during_manifest_scan(): bool
{
return false;
}
/**
* Check JavaScript files for JQHTML event handler violations
*/
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Only check JavaScript files
if (!str_ends_with($file_path, '.js')) {
return;
}
// Get JavaScript class from manifest metadata
$js_classes = [];
if (isset($metadata['class']) && isset($metadata['extension']) && $metadata['extension'] === 'js') {
$js_classes = [$metadata['class']];
}
// If no classes in metadata, nothing to check
if (empty($js_classes)) {
return;
}
// Find the first class that extends Jqhtml_Component
$class_name = null;
foreach ($js_classes as $js_class) {
if (Manifest::js_is_subclass_of($js_class, 'Jqhtml_Component')) {
$class_name = $js_class;
break;
}
}
if (!$class_name) {
return; // No JQHTML components found
}
// Look for corresponding .jqhtml template file
$dir = dirname($file_path);
$possible_templates = [
$dir . '/' . $class_name . '.jqhtml',
// Convert CamelCase to snake_case for template name
$dir . '/' . $this->to_snake_case($class_name) . '.jqhtml',
// Check without _component suffix if present
$dir . '/' . str_replace('_component', '', $this->to_snake_case($class_name)) . '.jqhtml',
];
$template_content = null;
$template_path = null;
foreach ($possible_templates as $template_file) {
if (file_exists($template_file)) {
$template_content = file_get_contents($template_file);
$template_path = $template_file;
break;
}
}
if (!$template_content) {
// No template found, can't verify usage
return;
}
// Find all methods that use event.preventDefault()
$lines = explode("\n", $contents);
$in_method = false;
$method_name = null;
$method_start_line = 0;
$method_param = null;
$brace_count = 0;
foreach ($lines as $line_num => $line) {
$line_number = $line_num + 1;
// Check for method definition with single parameter
if (preg_match('/^\s*(\w+)\s*\(\s*(\w+)\s*\)\s*{/', $line, $method_match)) {
$in_method = true;
$method_name = $method_match[1];
$method_param = $method_match[2];
$method_start_line = $line_number;
$brace_count = 1;
} elseif ($in_method) {
// Count braces to track method boundaries
$brace_count += substr_count($line, '{');
$brace_count -= substr_count($line, '}');
if ($brace_count <= 0) {
$in_method = false;
$method_name = null;
$method_param = null;
continue;
}
// Check if this line calls preventDefault on the parameter
if ($method_param && preg_match('/\b' . preg_quote($method_param, '/') . '\s*\.\s*preventDefault\s*\(/', $line)) {
// Found preventDefault usage, now check if this method is used in template
if ($this->method_used_in_template($template_content, $method_name)) {
$this->add_violation(
$file_path,
$line_number,
"JQHTML event handlers should not use event.preventDefault() - Method '{$method_name}()' is called from JQHTML template but tries to use event.preventDefault(). " .
"JQHTML automatically handles preventDefault and doesn't pass event objects.",
trim($line),
"Remove '{$method_param}' parameter and '{$method_param}.preventDefault()' call",
'high'
);
}
}
}
}
}
/**
* Check if a method is referenced in JQHTML template event attributes
*/
private function method_used_in_template(string $template_content, string $method_name): bool
{
// Look for @event=this.method_name or @event=method_name patterns
// Common events: @click, @change, @submit, @keyup, @keydown, @focus, @blur
$pattern = '/@\w+\s*=\s*(?:this\.)?' . preg_quote($method_name, '/') . '\b/';
return preg_match($pattern, $template_content) > 0;
}
/**
* Convert CamelCase/PascalCase to snake_case
*/
private function to_snake_case(string $str): string
{
$str = preg_replace('/([a-z])([A-Z])/', '$1_$2', $str);
return strtolower($str);
}
}

View File

@@ -0,0 +1,330 @@
<?php
namespace App\RSpade\CodeQuality\Rules\Jqhtml;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
/**
* JqhtmlInlineScriptRule - Enforces no inline scripts or styles in .jqhtml template files
*
* This rule checks .jqhtml component template files for inline <script> or <style> tags
* and provides remediation instructions for creating separate JS and SCSS files that
* follow Jqhtml component patterns.
*/
class JqhtmlInlineScript_CodeQualityRule extends CodeQualityRule_Abstract
{
public function get_id(): string
{
return 'JQHTML-INLINE-01';
}
public function get_name(): string
{
return 'Jqhtml Inline Script/Style Check';
}
public function get_description(): string
{
return 'Enforces no inline JavaScript or CSS in .jqhtml templates - must use separate component class and SCSS files';
}
public function get_file_patterns(): array
{
return ['*.jqhtml'];
}
public function get_default_severity(): string
{
return 'critical';
}
/**
* This rule should run during manifest scan to provide immediate feedback
*
* IMPORTANT: This method should ALWAYS return false unless explicitly requested
* by the framework developer. Manifest-time checks are reserved for critical
* framework convention violations that need immediate developer attention.
*
* Rules executed during manifest scan will run on every file change in development,
* potentially impacting performance. Only enable this for rules that:
* - Enforce critical framework conventions that would break the application
* - Need to provide immediate feedback before code execution
* - Have been specifically requested to run at manifest-time by framework maintainers
*
* DEFAULT: Always return false unless you have explicit permission to do otherwise.
*
* EXCEPTION: This rule has been explicitly approved to run at manifest-time because
* inline scripts/styles in Jqhtml files violate critical framework architecture patterns.
*/
public function is_called_during_manifest_scan(): bool
{
return true; // Explicitly approved for manifest-time checking
}
/**
* Process file during manifest update to extract inline script/style violations
*/
public function on_manifest_file_update(string $file_path, string $contents, array $metadata = []): ?array
{
$lines = explode("\n", $contents);
$violations = [];
foreach ($lines as $line_num => $line) {
$line_number = $line_num + 1;
// Check for <script> tags (excluding external script src)
if (preg_match('/<script\b[^>]*>(?!.*src=)/i', $line)) {
$violations[] = [
'type' => 'inline_script',
'line' => $line_number,
'code' => trim($line)
];
break; // Only need to find first violation
}
// Check for <style> tags
if (preg_match('/<style\b[^>]*>/i', $line)) {
$violations[] = [
'type' => 'inline_style',
'line' => $line_number,
'code' => trim($line)
];
break; // Only need to find first violation
}
}
if (!empty($violations)) {
return ['jqhtml_inline_violations' => $violations];
}
return null;
}
/**
* Check jqhtml file for inline script/style violations stored in metadata
*/
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Check for violations in code quality metadata
if (isset($metadata['code_quality_metadata']['JQHTML-INLINE-01']['jqhtml_inline_violations'])) {
$violations = $metadata['code_quality_metadata']['JQHTML-INLINE-01']['jqhtml_inline_violations'];
// Throw on first violation
foreach ($violations as $violation) {
$component_id = $this->extract_component_id($contents);
if ($violation['type'] === 'inline_script') {
$error_message = "Code Quality Violation (JQHTML-INLINE-01) - Inline Script in Jqhtml Template\n\n";
$error_message .= "CRITICAL: Inline <script> tags are not allowed in .jqhtml templates\n\n";
$error_message .= "File: {$file_path}\n";
$error_message .= "Line: {$violation['line']}\n";
$error_message .= "Code: {$violation['code']}\n\n";
$error_message .= $this->get_script_remediation($file_path, $component_id);
} else {
$error_message = "Code Quality Violation (JQHTML-INLINE-01) - Inline Style in Jqhtml Template\n\n";
$error_message .= "CRITICAL: Inline <style> tags are not allowed in .jqhtml templates\n\n";
$error_message .= "File: {$file_path}\n";
$error_message .= "Line: {$violation['line']}\n";
$error_message .= "Code: {$violation['code']}\n\n";
$error_message .= $this->get_style_remediation($file_path, $component_id);
}
throw new \App\RSpade\CodeQuality\RuntimeChecks\YoureDoingItWrongException(
$error_message,
0,
null,
base_path($file_path),
$violation['line']
);
}
}
}
/**
* Extract component ID from jqhtml file content
* Looks for <Define:ComponentName> pattern
*/
private function extract_component_id(string $contents): ?string
{
if (preg_match('/<Define:([A-Z][A-Za-z0-9_]*)>/', $contents, $matches)) {
return $matches[1];
}
return null;
}
/**
* Get detailed remediation instructions for scripts
*/
private function get_script_remediation(string $file_path, ?string $component_id): string
{
// Determine the JS filename and class name
$path_parts = pathinfo($file_path);
$base_name = $path_parts['filename'];
// Use component ID if available, otherwise use filename
$class_name = $component_id ?: str_replace('_', '', ucwords($base_name, '_'));
$js_filename = $path_parts['filename'] . '.js';
$js_path = dirname($file_path) . '/' . $js_filename;
return "FRAMEWORK CONVENTION: JavaScript for jqhtml components must be in separate ES6 class files.
REQUIRED STEPS:
1. Create a JavaScript file: {$js_path}
2. Name the ES6 class exactly: {$class_name}
3. Extend Jqhtml_Component base class
4. Implement lifecycle methods: on_create(), on_load(), on_ready()
EXAMPLE IMPLEMENTATION for {$js_filename}:
/**
* Component class for {$class_name}
*/
class {$class_name} extends Jqhtml_Component {
/**
* Called when component instance is created
* Use for initial setup and event binding
*/
async on_create() {
// Initialize component state
this.state = {
count: 0,
loading: false
};
// Bind events to elements with \$id attribute in template
// Example: <button \$onclick=\"handle_click\">Click Me</button>
}
/**
* Called to load data (before rendering)
* Use for async data fetching - NO DOM manipulation here
*/
async on_load() {
// Fetch any required data
// this.data contains data passed to component
// Example:
// const response = await fetch('/api/data');
// this.remote_data = await response.json();
}
/**
* Called after component is fully rendered and ready
* Use for final DOM setup
*/
async on_ready() {
// Component is fully loaded and rendered
// The \$. property gives you the jQuery element
this.\$.addClass('loaded');
// Access template elements via \$id
// Example: this.\$.find('[data-id=\"title\"]')
}
// Event handlers referenced in template
handle_click(event) {
this.state.count++;
this.render(); // Re-render component with new state
}
}
KEY CONVENTIONS:
- Class name MUST match the <Define:{$class_name}> in the .jqhtml file
- MUST extend Jqhtml_Component base class
- Use lifecycle methods: on_create(), on_load(), on_ready()
- Access component element via this.\$
- Bind events using \$onclick, \$onchange, etc. in template
- Use this.render() to re-render with updated state
- NO inline scripts in the .jqhtml template
WHY THIS MATTERS:
- Separation of concerns: Template structure separate from behavior
- Component reusability: Clean component architecture
- Framework integration: Automatic lifecycle management
- Testability: JavaScript logic can be tested independently
- Performance: Components only initialize when needed";
}
/**
* Get detailed remediation instructions for styles
*/
private function get_style_remediation(string $file_path, ?string $component_id): string
{
// Determine the SCSS filename and class name
$path_parts = pathinfo($file_path);
$scss_filename = $path_parts['filename'] . '.scss';
$scss_path = dirname($file_path) . '/' . $scss_filename;
// Use component ID if available, otherwise use filename
$class_name = $component_id ?: str_replace('_', '', ucwords($path_parts['filename'], '_'));
return "FRAMEWORK CONVENTION: Styles for jqhtml components must be in separate SCSS files.
REQUIRED STEPS:
1. Create a SCSS file: {$scss_path}
2. Wrap ALL styles in .{$class_name} selector
3. Every instance of the component will have class=\"{$class_name}\" automatically
EXAMPLE IMPLEMENTATION for {$scss_filename}:
/**
* Styles for {$class_name} component
*/
.{$class_name} {
// All component styles MUST be nested within this class
// This ensures styles are scoped to this component only
// Component container styles
display: block;
padding: 1rem;
border: 1px solid \$border-color;
border-radius: 4px;
// Child element styles
.header {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.content {
padding: 0.5rem 0;
.item {
margin: 0.25rem 0;
}
}
// State-based styles
&.loaded {
opacity: 1;
transition: opacity 0.3s;
}
&.loading {
opacity: 0.5;
pointer-events: none;
}
// Responsive styles
@media (max-width: 768px) {
padding: 0.5rem;
}
}
KEY CONVENTIONS:
- ALL styles MUST be nested within .{$class_name} { }
- Component automatically gets class=\"{$class_name}\" on root element
- Use SCSS nesting for child elements
- Use & for state modifiers (&.loaded, &.active)
- Import shared variables if needed (\$border-color, etc.)
- NO inline styles in the .jqhtml template
- NO global styles that could affect other components
WHY THIS MATTERS:
- Style encapsulation: Component styles don't leak to other components
- Predictable cascade: Clear style hierarchy within component
- Reusability: Component can be used multiple times without conflicts
- Maintainability: Styles organized with their components
- Framework convention: Consistent pattern across all components";
}
}