Skip to content

Commit cf6c954

Browse files
authored
release: Pipeline Templates, Quality Scoring, Competitor Differentiation (v0.10.0)
Merged feature branches: - #52 Pipeline Templates - #53 Quality Scoring - #37 Competitor-Aware Content Differentiation
1 parent 736857e commit cf6c954

205 files changed

Lines changed: 19718 additions & 287 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 116 additions & 281 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ Each stage is a queued job. The pipeline is event-driven. Stages are defined in
3737

3838
## Features
3939

40+
### AI Content Quality Scoring
41+
**New in v0.10.0.** Automated multi-dimensional content quality analysis across five dimensions:
42+
- **Readability** — Flesch-Kincaid metrics, sentence and word complexity
43+
- **SEO** — Keyword density, heading structure, meta optimization
44+
- **Brand Consistency** — LLM-powered brand voice and tone analysis
45+
- **Factual Accuracy** — Cross-referenced claim validation
46+
- **Engagement Prediction** — AI-predicted engagement score
47+
48+
Features: real-time score ring in the editor sidebar, quality dashboard with Chart.js trend visualization, space leaderboard, configurable pipeline quality gates, auto-score on publish, and a `quality.scored` webhook event.
49+
50+
4051
### Content Generation Pipeline
4152
Submit a brief → AI agents generate, illustrate, optimize, and quality-gate content → auto-publish or human review.
4253

@@ -96,8 +107,28 @@ Manage webhook endpoints and event subscriptions directly from the admin panel (
96107
- See [docs/graphql-api.md](docs/graphql-api.md) for the full guide
97108

98109

99-
### Plugin & Extension System
100-
First-class plugin architecture. Extend pipelines, register custom LLM providers, add admin UI, and react to content events — all from a self-contained plugin package.
110+
### AI Pipeline Templates & Preset Library
111+
**New in v0.10.0.** Reusable AI pipeline templates for accelerated content creation workflows.
112+
113+
**Features:**
114+
- **8 built-in templates:** Blog Post Pipeline, Social Media Campaign, Product Description, Email Newsletter, Press Release, Landing Page, Technical Documentation, Video Script
115+
- **Template library API:** Browse, rate, and install templates from community library
116+
- **Space-scoped templates:** Custom templates per content space with RBAC
117+
- **One-click install wizard:** Auto-configures personas, stages, and variables from template schema
118+
- **Template versioning:** Version management with changelog and rollback support
119+
- **Template packs:** Plugin-registered template collections with metadata
120+
- **Plugin hooks:** `registerTemplateCategory()` and `registerTemplatePack()` for extending the library
121+
- **Template ratings:** Community feedback and quality metrics
122+
123+
**Endpoints:**
124+
- `GET /api/v1/spaces/{space}/pipeline-templates` — List templates
125+
- `POST /api/v1/spaces/{space}/pipeline-templates` — Create custom template
126+
- `POST /api/v1/spaces/{space}/pipeline-templates/{template}/publish` — Publish template
127+
- `POST /api/v1/spaces/{space}/pipeline-templates/{template}/versions` — Create new version
128+
- `POST /api/v1/spaces/{space}/pipeline-templates/installs/{version}` — Install template
129+
- `POST /api/v1/spaces/{space}/pipeline-templates/{template}/ratings` — Rate template
130+
131+
See [docs/pipeline-templates.md](docs/pipeline-templates.md) for the complete feature guide.
101132

102133
### Plugin & Extension System
103134
First-class plugin architecture. Extend pipelines, register custom LLM providers, add admin UI, and react to content events — all from a self-contained plugin package.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace App\Events\Quality;
4+
5+
use App\Models\ContentQualityScore;
6+
use Illuminate\Foundation\Events\Dispatchable;
7+
use Illuminate\Queue\SerializesModels;
8+
9+
class ContentQualityScored
10+
{
11+
use Dispatchable;
12+
use SerializesModels;
13+
14+
public function __construct(
15+
public readonly ContentQualityScore $score,
16+
) {}
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\GraphQL\Mutations;
4+
5+
use App\Models\CompetitorAlert;
6+
7+
class CreateCompetitorAlert
8+
{
9+
/** @param array{input: array<string, mixed>} $args */
10+
public function __invoke(mixed $root, array $args): CompetitorAlert
11+
{
12+
return CompetitorAlert::create($args['input']);
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\GraphQL\Mutations;
4+
5+
use App\Models\CompetitorSource;
6+
7+
class CreateCompetitorSource
8+
{
9+
/** @param array{input: array<string, mixed>} $args */
10+
public function __invoke(mixed $root, array $args): CompetitorSource
11+
{
12+
return CompetitorSource::create($args['input']);
13+
}
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\GraphQL\Mutations;
4+
5+
use App\Models\CompetitorAlert;
6+
7+
class DeleteCompetitorAlert
8+
{
9+
/** @param array{id: string} $args */
10+
public function __invoke(mixed $root, array $args): ?CompetitorAlert
11+
{
12+
$alert = CompetitorAlert::find($args['id']);
13+
14+
if ($alert) {
15+
$currentSpace = app()->bound('current_space') ? app('current_space') : null;
16+
abort_if($currentSpace && $alert->space_id !== $currentSpace->id, 403);
17+
18+
$alert->delete();
19+
}
20+
21+
return $alert;
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\GraphQL\Mutations;
4+
5+
use App\Models\CompetitorSource;
6+
7+
class DeleteCompetitorSource
8+
{
9+
/** @param array{id: string} $args */
10+
public function __invoke(mixed $root, array $args): ?CompetitorSource
11+
{
12+
$source = CompetitorSource::find($args['id']);
13+
14+
if ($source) {
15+
$currentSpace = app()->bound('current_space') ? app('current_space') : null;
16+
abort_if($currentSpace && $source->space_id !== $currentSpace->id, 403);
17+
18+
$source->delete();
19+
}
20+
21+
return $source;
22+
}
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\GraphQL\Mutations;
4+
5+
use App\Jobs\CrawlCompetitorSourceJob;
6+
use App\Models\CompetitorSource;
7+
8+
class TriggerCompetitorCrawl
9+
{
10+
/** @param array{source_id: string} $args */
11+
public function __invoke(mixed $root, array $args): bool
12+
{
13+
$source = CompetitorSource::findOrFail($args['source_id']);
14+
15+
$currentSpace = app()->bound('current_space') ? app('current_space') : null;
16+
abort_if($currentSpace && $source->space_id !== $currentSpace->id, 403);
17+
18+
CrawlCompetitorSourceJob::dispatch($source);
19+
20+
return true;
21+
}
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\GraphQL\Mutations;
4+
5+
use App\Models\CompetitorSource;
6+
7+
class UpdateCompetitorSource
8+
{
9+
/** @param array{id: string, input: array<string, mixed>} $args */
10+
public function __invoke(mixed $root, array $args): CompetitorSource
11+
{
12+
$source = CompetitorSource::findOrFail($args['id']);
13+
14+
$currentSpace = app()->bound('current_space') ? app('current_space') : null;
15+
abort_if($currentSpace && $source->space_id !== $currentSpace->id, 403);
16+
17+
$source->update($args['input']);
18+
19+
return $source->fresh() ?? $source;
20+
}
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\GraphQL\Queries;
4+
5+
use App\Models\CompetitorContentItem;
6+
use Illuminate\Pagination\LengthAwarePaginator;
7+
8+
class CompetitorContent
9+
{
10+
/** @param array{space_id: string, source_id?: string|null, first?: int, page?: int} $args */
11+
public function __invoke(mixed $root, array $args): LengthAwarePaginator
12+
{
13+
$query = CompetitorContentItem::query()
14+
->whereHas('source', fn ($q) => $q->where('space_id', $args['space_id']))
15+
->with('source')
16+
->orderByDesc('crawled_at');
17+
18+
if (! empty($args['source_id'])) {
19+
$query->where('source_id', $args['source_id']);
20+
}
21+
22+
$perPage = (int) ($args['first'] ?? 20);
23+
$page = (int) ($args['page'] ?? 1);
24+
25+
return $query->paginate($perPage, ['*'], 'page', $page);
26+
}
27+
}

0 commit comments

Comments
 (0)