feat: add Writing Instructions setting for LLM system prompt (v0.4.4)

New textarea in Content settings lets admins provide custom instructions
appended to the writer's system prompt. Steers style, structure, voice,
and formatting without editing code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
peptiderepo
2026-04-19 17:01:54 +07:00
parent 65560ea0b5
commit b0cc93669d
6 changed files with 66 additions and 19 deletions

View File

@@ -5,6 +5,14 @@ All notable changes to PRAutoBlogger will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project uses [Semantic Versioning](https://semver.org/).
## [0.4.4] — 2026-04-19
### Added
- **Writing Instructions setting.** New textarea in Content settings lets you
provide custom instructions appended to the LLM's system prompt when writing
articles. Use it to steer style, structure, voice, and formatting without
editing code.
## [0.4.3] — 2026-04-19
### Fixed

View File

@@ -202,6 +202,14 @@ class PRAutoBlogger_Settings_Fields {
'default' => 2000,
'min' => 500,
],
[
'id' => 'prautoblogger_writing_instructions',
'label' => __( 'Writing Instructions', 'prautoblogger' ),
'type' => 'textarea',
'section' => 'prautoblogger_content',
'default' => '',
'description' => __( 'Custom instructions appended to the LLM\'s system prompt when writing articles. Use this to steer style, structure, voice, and formatting — e.g. "Always open with a hook question. Use short paragraphs. Cite at least two studies."', 'prautoblogger' ),
],
[
'id' => 'prautoblogger_topic_exclusions',
'label' => __( 'Topic Exclusions', 'prautoblogger' ),

View File

@@ -32,13 +32,10 @@ class PRAutoBlogger_Content_Generator {
* Generate a blog post from an article idea.
*
* Reads pipeline mode from settings and dispatches to the appropriate method.
*
* Side effects: LLM API calls (1-3 depending on mode), cost logging.
*
* @param PRAutoBlogger_Article_Idea $idea The scored idea to generate content for.
*
* @return string Generated HTML content.
*
* @throws \RuntimeException On generation failure.
*/
public function generate( PRAutoBlogger_Article_Idea $idea ): string {
@@ -51,7 +48,8 @@ class PRAutoBlogger_Content_Generator {
absint( get_option( 'prautoblogger_min_word_count', 800 ) ),
absint( get_option( 'prautoblogger_max_word_count', 2000 ) ),
get_option( 'prautoblogger_niche_description', '' ),
json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: []
json_decode( get_option( 'prautoblogger_topic_exclusions', '[]' ), true ) ?: [],
get_option( 'prautoblogger_writing_instructions', '' )
);
if ( 'single_pass' === $mode ) {
@@ -64,8 +62,7 @@ class PRAutoBlogger_Content_Generator {
/**
* Single-pass generation: one LLM call produces the complete article.
*
* @param PRAutoBlogger_Content_Request $request
*
* @param PRAutoBlogger_Content_Request $request Content request with settings.
* @return string Generated HTML content.
*/
private function generate_single_pass( PRAutoBlogger_Content_Request $request ): string {
@@ -249,8 +246,7 @@ class PRAutoBlogger_Content_Generator {
/**
* Build the system prompt shared across all writing stages.
*
* @param PRAutoBlogger_Content_Request $request
*
* @param PRAutoBlogger_Content_Request $request Content request with settings.
* @return string
*/
private function build_writer_system_prompt( PRAutoBlogger_Content_Request $request ): string {
@@ -262,6 +258,13 @@ class PRAutoBlogger_Content_Generator {
$prompt .= ". Write well-researched, engaging, SEO-friendly content. ";
$prompt .= "Use a {$request->get_tone()} tone. ";
$prompt .= "Output HTML content only — no markdown, no code fences, no commentary.";
// Append user-defined writing instructions if configured.
$instructions = trim( $request->get_writing_instructions() );
if ( '' !== $instructions ) {
$prompt .= "\n\nAdditional instructions:\n" . $instructions;
}
return $prompt;
}

View File

@@ -21,6 +21,7 @@ class PRAutoBlogger_Content_Request {
private int $max_word_count;
private string $niche_description;
private array $topic_exclusions;
private string $writing_instructions;
public function __construct(
PRAutoBlogger_Article_Idea $idea,
@@ -29,15 +30,17 @@ class PRAutoBlogger_Content_Request {
int $min_word_count,
int $max_word_count,
string $niche_description,
array $topic_exclusions = []
array $topic_exclusions = [],
string $writing_instructions = ''
) {
$this->idea = $idea;
$this->pipeline_mode = $pipeline_mode;
$this->tone = $tone;
$this->min_word_count = $min_word_count;
$this->max_word_count = $max_word_count;
$this->niche_description = $niche_description;
$this->topic_exclusions = $topic_exclusions;
$this->idea = $idea;
$this->pipeline_mode = $pipeline_mode;
$this->tone = $tone;
$this->min_word_count = $min_word_count;
$this->max_word_count = $max_word_count;
$this->niche_description = $niche_description;
$this->topic_exclusions = $topic_exclusions;
$this->writing_instructions = $writing_instructions;
}
public function get_idea(): PRAutoBlogger_Article_Idea {
@@ -67,4 +70,8 @@ class PRAutoBlogger_Content_Request {
public function get_topic_exclusions(): array {
return $this->topic_exclusions;
}
public function get_writing_instructions(): string {
return $this->writing_instructions;
}
}

View File

@@ -8,7 +8,7 @@ declare(strict_types=1);
* Plugin Name: PRAutoBlogger
* Plugin URI: https://peptiderepo.com/prautoblogger
* Description: Monitors social media for trending topics, generates SEO-friendly blog posts using AI, and publishes them on a daily schedule with full cost tracking and self-improvement metrics.
* Version: 0.4.3
* Version: 0.4.4
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: PeptideRepo
@@ -35,7 +35,7 @@ if ( ! defined( 'ABSPATH' ) ) {
| and limits without magic strings.
*/
define( 'PRAUTOBLOGGER_VERSION', '0.4.3' );
define( 'PRAUTOBLOGGER_VERSION', '0.4.4' );
define( 'PRAUTOBLOGGER_DB_VERSION', '1.1.0' );
define( 'PRAUTOBLOGGER_PLUGIN_FILE', __FILE__ );
define( 'PRAUTOBLOGGER_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );

View File

@@ -25,7 +25,8 @@ class ContentRequestTest extends BaseTestCase {
1000,
2500,
'Technology and innovation',
[ 'politics', 'religion' ]
[ 'politics', 'religion' ],
'Always open with a hook question.'
);
$this->assertSame( $idea, $request->get_idea() );
@@ -35,6 +36,7 @@ class ContentRequestTest extends BaseTestCase {
$this->assertSame( 2500, $request->get_max_word_count() );
$this->assertSame( 'Technology and innovation', $request->get_niche_description() );
$this->assertSame( [ 'politics', 'religion' ], $request->get_topic_exclusions() );
$this->assertSame( 'Always open with a hook question.', $request->get_writing_instructions() );
}
/**
@@ -63,6 +65,25 @@ class ContentRequestTest extends BaseTestCase {
$this->assertIsArray( $request->get_topic_exclusions() );
}
/**
* Test writing_instructions defaults to empty string when omitted.
*/
public function test_writing_instructions_defaults_to_empty(): void {
$idea_data = $this->get_article_idea_fixture();
$idea = new \PRAutoBlogger_Article_Idea( $idea_data );
$request = new \PRAutoBlogger_Content_Request(
$idea,
'auto',
'professional',
1000,
2500,
'Tech'
);
$this->assertSame( '', $request->get_writing_instructions() );
}
/**
* Test with empty topic exclusions.
*/