add tagging

This commit is contained in:
team 1
2026-02-21 16:23:34 +01:00
parent 5a3852db12
commit cf5b473034
23 changed files with 1984 additions and 85 deletions

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Entity\TagRebuildJob;
use App\Tag\TagNdjsonExporter;
use App\Tag\TagVectorIndexBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'mto:agent:tags:job:run',
description: 'Run a single tag rebuild job (export tags.ndjson + build vector_tags.index) with lock'
)]
final class TagRebuildRunJobCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly TagNdjsonExporter $exporter,
private readonly TagVectorIndexBuilder $builder,
private readonly string $lockFilePath,
) {
parent::__construct();
}
protected function configure(): void
{
$this->addArgument('jobId', InputArgument::REQUIRED, 'TagRebuildJob UUID');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$jobId = (string)$input->getArgument('jobId');
/** @var TagRebuildJob|null $job */
$job = $this->em->getRepository(TagRebuildJob::class)->find($jobId);
if (!$job instanceof TagRebuildJob) {
$output->writeln('<error>Job not found.</error>');
return Command::FAILURE;
}
// ---------------------------------------------------------
// Global lock to avoid parallel rebuilds
// ---------------------------------------------------------
$lockDir = \dirname($this->lockFilePath);
if (!\is_dir($lockDir)) {
@\mkdir($lockDir, 0775, true);
}
$fh = @\fopen($this->lockFilePath, 'c+');
if (!$fh) {
$job->markFailed('Cannot open lock file: ' . $this->lockFilePath);
$this->em->flush();
$output->writeln('<error>Cannot open lock file.</error>');
return Command::FAILURE;
}
// If another rebuild runs, we fail fast (simple & safe).
if (!@\flock($fh, LOCK_EX | LOCK_NB)) {
\fclose($fh);
$job->markFailed('Another tag rebuild is currently running (lock busy).');
$this->em->flush();
$output->writeln('<error>Lock busy. Another rebuild is running.</error>');
return Command::FAILURE;
}
// mark running
$job->markRunning();
$this->em->flush();
try {
$export = $this->exporter->export();
$this->builder->build();
$job->markCompleted();
$this->em->flush();
$output->writeln('<info>OK</info>');
$output->writeln('tags.ndjson: ' . $export['path']);
} catch (\Throwable $e) {
$job->markFailed($e->getMessage());
$this->em->flush();
$output->writeln('<error>FAILED: ' . $e->getMessage() . '</error>');
@\flock($fh, LOCK_UN);
@\fclose($fh);
return Command::FAILURE;
}
@\flock($fh, LOCK_UN);
@\fclose($fh);
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Tag\TagNdjsonExporter;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'mto:agent:tags:export',
description: 'Export tags to NDJSON for tag vector routing'
)]
final class TagsExportCommand extends Command
{
public function __construct(
private TagNdjsonExporter $exporter,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
$result = $this->exporter->export();
} catch (\Throwable $e) {
$output->writeln('<error>ERROR: ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
$output->writeln('<info>Tags NDJSON exported</info>');
$output->writeln('Path: ' . $result['path']);
$output->writeln('Tags: ' . $result['tags']);
$output->writeln('Lines: ' . $result['lines']);
$output->writeln('Bytes: ' . $result['bytes']);
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace App\Command;
use App\Tag\TagNdjsonExporter;
use App\Tag\TagVectorIndexBuilder;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(
name: 'mto:agent:tags:rebuild',
description: 'Export tags.ndjson and rebuild tag vector index (vector_tags.index)'
)]
final class TagsRebuildCommand extends Command
{
public function __construct(
private readonly TagNdjsonExporter $exporter,
private readonly TagVectorIndexBuilder $builder,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
try {
$export = $this->exporter->export();
$output->writeln('<info>1/2 Exported tags.ndjson</info>');
$output->writeln('Path: ' . $export['path']);
$output->writeln('Tags: ' . $export['tags']);
$output->writeln('Lines: ' . $export['lines']);
$output->writeln('Bytes: ' . $export['bytes']);
$this->builder->build();
$output->writeln('<info>2/2 Built vector_tags.index</info>');
} catch (\Throwable $e) {
$output->writeln('<error>ERROR: ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
return Command::SUCCESS;
}
}