Skip to content
wiki.fftac.org

Class Source Parser - Source Excerpt 02

Back to Class Source Parser

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiral-script/includes/class-source-parser.php so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/plugins/spiral-script/includes/class-source-parser.php

if (preg_match('/^step\s+([a-zA-Z][a-zA-Z0-9_]*)\s+uses\s+([^\s]+)(?:\s+after\s+([a-zA-Z][a-zA-Z0-9_]*))?/i', $line, $match) === 1) {
            if (!isset($record['workflow']) || !is_array($record['workflow'])) {
                $record['workflow'] = ['steps' => [], 'gates' => []];
            }
            $record['workflow']['steps'][] = [
                'name' => $match[1],
                'uses' => $match[2],
                'after' => (string) ($match[3] ?? ''),
            ];
            return;
        }

        if (preg_match('/^gate\s+([a-zA-Z][a-zA-Z0-9_]*)\s+requires\s+(.+)$/i', $line, $match) === 1) {
            if (!isset($record['workflow']) || !is_array($record['workflow'])) {
                $record['workflow'] = ['steps' => [], 'gates' => []];
            }
            $record['workflow']['gates'][] = [
                'name' => $match[1],
                'requires' => self::clean_scalar($match[2]),
            ];
        }
    }

    /**
     * @param array<string,mixed> $defaults
     * @return array<string,mixed>
     */
    private static function base_record(array $defaults): array
    {
        $now = (string) ($defaults['createdAt'] ?? gmdate('c'));
        $created_by = (string) ($defaults['createdBy'] ?? 'user:local');

        return [
            'scriptId' => (string) ($defaults['scriptId'] ?? ''),
            'version' => (string) ($defaults['version'] ?? '0.1.0'),
            'title' => (string) ($defaults['title'] ?? 'Untitled Spiral Script'),
            'visibility' => (string) ($defaults['visibility'] ?? 'private'),
            'authorMode' => (string) ($defaults['authorMode'] ?? 'human'),
            'sourceReferences' => [],
            'intent' => (string) ($defaults['intent'] ?? ''),
            'inputs' => [],
            'symbolicFrame' => [],
            'transformationRules' => [],
            'outputContract' => [
                'format' => 'markdown.sections',
                'sections' => [],
                'requirements' => [],
            ],
            'constraints' => [],
            'safetyRules' => [],
            'validationRules' => [],
            'provenance' => [
                'createdBy' => $created_by,
                'createdAt' => $now,
                'rights' => (string) ($defaults['rights'] ?? 'unspecified'),
                'reviewState' => (string) ($defaults['reviewState'] ?? 'draft'),
            ],
            'runSettings' => [
                'modelPolicy' => (string) ($defaults['modelPolicy'] ?? 'site-default'),
                'temperature' => (float) ($defaults['temperature'] ?? 0.4),
                'maxOutputTokens' => (int) ($defaults['maxOutputTokens'] ?? 1200),
                'storeRunLog' => true,
            ],
            'revisionHistory' => [],
            'forkPolicy' => [
                'allowForks' => (bool) ($defaults['allowForks'] ?? false),
                'requiresAttribution' => true,
                'allowedVisibility' => ['private'],
            ],
            'publicationStatus' => [
                'status' => 'draft',
                'lane' => 'private_workspace',
            ],
        ];
    }

    /**
     * @return array{0:string,1:string}
     */
    private static function split_script_id(string $value, string $default_version): array
    {
        $value = trim($value);
        if (preg_match('/^(ss:[a-z0-9.-]+)@([0-9]+\.[0-9]+\.[0-9]+(?:\+[a-z0-9.-]+)?)$/i', $value, $match) === 1) {
            $version = preg_replace('/\+.*/', '', $match[2]) ?: $default_version;

            return [$match[1] . '@' . $match[2], $version];
        }

        if (strpos($value, 'ss:') === 0) {
            return [$value . '@' . $default_version, $default_version];
        }

        return [self::default_script_id($value, $default_version), $default_version];
    }

    private static function default_script_id(string $title, string $version): string
    {
        $slug = strtolower(trim((string) preg_replace('/[^a-zA-Z0-9]+/', '-', $title), '-'));
        if ($slug === '') {
            $slug = 'untitled';
        }

        return 'ss:draft.' . $slug . '@' . $version;
    }

    private static function title_from_script_id(string $script_id): string
    {
        $slug = preg_replace('/^ss:[a-z0-9.-]*?([a-z0-9-]+)@.*$/', '$1', $script_id) ?: 'Spiral Script';
        $title = ucwords(str_replace('-', ' ', (string) $slug));

        return $title !== '' ? $title : 'Spiral Script';
    }

    /**
     * @param array<string,mixed> $record
     */
    private static function add_source_reference(array &$record, string $type, string $raw_ref): void
    {
        if ($type === 'rights') {
            $record['provenance']['rights'] = $raw_ref;
            return;
        }

        $ref = $raw_ref;
        if (preg_match('/^(folio|url|user|prompt)\("([^"]+)"\)$/i', $raw_ref, $match) === 1) {
            $ref = strtolower($type) === 'manuscript' ? 'folio:' . $match[2] : $match[2];
        }

        $access = strtolower($type) === 'user_note' || strtolower($type) === 'upload' ? 'private' : 'public';
        $record['sourceReferences'][] = [
            'type' => $type,
            'ref' => $ref,
            'label' => $raw_ref,
            'access' => $access,
            'rights' => '',
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private static function symbol_frame(string $item): array
    {
        $resolved = LanguageSpec::resolve_symbol($item);
        if (is_array($resolved)) {
            return [
                'symbol' => (string) ($resolved['symbol'] ?? ''),
                'name' => (string) ($resolved['name'] ?? $item),
                'machineMeaning' => (string) ($resolved['machineMeaning'] ?? ''),
                'note' => self::clean_scalar($item),
            ];
        }

        return [
            'symbol' => '',
            'name' => self::clean_scalar($item),
            'machineMeaning' => '',
            'note' => self::clean_scalar($item),
        ];
    }

    /**
     * @return array<int,string>
     */
    private static function parse_list(string $value): array
    {
        $value = trim($value);
        if ($value === '') {
            return [];
        }

        if (preg_match('/^[a-z_]+\((.*)\)$/i', $value, $match) === 1) {
            $value = $match[1];
        }

        $value = trim($value);
        if (strlen($value) >= 2 && $value[0] === '[' && substr($value, -1) === ']') {
            $value = substr($value, 1, -1);
        }

        if (preg_match_all('/"([^"]+)"/', $value, $matches) && !empty($matches[1])) {
            return array_values(array_filter(array_map([self::class, 'clean_scalar'], $matches[1])));
        }

        return array_values(array_filter(array_map(
            static fn ($item): string => self::clean_scalar((string) $item),
            explode(',', $value)
        )));
    }

    /**
     * @return array<string,mixed>
     */
    private static function parse_output_contract(string $value): array
    {
        $value = trim($value);
        $contract = [
            'format' => self::clean_scalar($value),
            'sections' => [],
            'requirements' => [],
        ];

        if (preg_match('/^([a-zA-Z0-9_.:-]+)\s*\((.*)\)$/', $value, $match) === 1) {
            $contract['format'] = $match[1];
            if (stripos($match[1], 'sections') !== false) {
                $contract['sections'] = self::parse_list($match[2]);
            }
        }

        return $contract;
    }

    /**
     * @param array<string,mixed> $record
     */
    private static function parse_safety_rules(array &$record, string $value): void
    {
        $value = trim($value);
        if (preg_match('/^deny\((.*)\)$/i', $value, $match) === 1) {
            foreach (self::parse_list($match[1]) as $rule) {
                $record['safetyRules'][] = 'deny ' . $rule;
            }
            return;
        }

        foreach (self::parse_list($value) as $rule) {
            $record['safetyRules'][] = $rule;
        }
    }

    /**
     * @param array<string,mixed> $record
     */
    private static function parse_validation_rules(array &$record, string $value): void
    {
        $value = trim($value);
        if (preg_match('/^require\((.*)\)$/i', $value, $match) === 1) {
            foreach (self::parse_list($match[1]) as $rule) {
                $record['validationRules'][] = [
                    'rule' => $rule,
                    'severity' => 'blocker',
                    'message' => 'Required by Spiral Script source.',
                ];
            }
            return;
        }

        foreach (self::parse_list($value) as $rule) {
            $record['validationRules'][] = [