agent-prose 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ """
2
+ agent-prose: Deterministic Anti-RLHF Stylometric Gate for Autonomous Agents.
3
+ Created and maintained by Nitivra.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ __version__ = "1.0.0"
8
+ __author__ = "Nitivra"
9
+
10
+ from agent_prose.engine import ProseEngine
11
+ from agent_prose.profiles import get_profile, resolve_locale
12
+
13
+ __all__ = ["ProseEngine", "get_profile", "resolve_locale", "__version__"]
@@ -0,0 +1,6 @@
1
+ """CLI entry point when invoked via python -m agent_prose."""
2
+ import sys
3
+ from agent_prose.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
@@ -0,0 +1,73 @@
1
+ """
2
+ Model-specific prompt priming adapters for agent-prose.
3
+ Created and maintained by Nitivra.
4
+
5
+ Provides calibration prompts to eliminate vendor-specific RLHF stylometric tells.
6
+ Supported model families:
7
+ - Anthropic Claude
8
+ - Google Gemini
9
+ - xAI Grok
10
+ - Meta Muse / Llama
11
+ - Microsoft Copilot
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Dict
16
+ from agent_prose.profiles.base import DialectProfile
17
+ from agent_prose.profiles import resolve_locale
18
+
19
+ ADAPTER_TEMPLATES: Dict[str, str] = {
20
+ "claude": (
21
+ "[WRITING INVARIANT] Ban prestige filler words: crucial, nuance, bedrock, "
22
+ "linchpin, cornerstone, bespoke, imperative. Do not use 'not only X, but also Y' "
23
+ "synthetic balance. Do not hedge using 'that being said' or 'to be sure'. "
24
+ "State technical trade-offs with numbers and precise dependencies."
25
+ ),
26
+ "gemini": (
27
+ "[WRITING INVARIANT] Vary sentence lengths deliberately (mix 6-word statements "
28
+ "with 24-word complex sentences). Avoid uniform bullet lists. Do not use "
29
+ "trailing participial clauses (', ...ing'). Limit sentences to at most two commas."
30
+ ),
31
+ "grok": (
32
+ "[WRITING INVARIANT] Eliminate informal conversational swagger ('Here is the kicker', "
33
+ "'Let us get into it'). Do not use raw flow arrows (->) in prose; write complete "
34
+ "sentences with active verbs. Maintain rigorous engineering delivery."
35
+ ),
36
+ "muse": (
37
+ "[WRITING INVARIANT] Use straight ASCII characters only (no em dashes or curly quotes). "
38
+ "Contractions are prohibited (write 'do not', 'cannot', 'it is'). "
39
+ "Ban conversational fillers and deliver factual analysis directly."
40
+ ),
41
+ "copilot": (
42
+ "[WRITING INVARIANT] Contractions are prohibited (write 'do not', 'cannot', 'it is'). "
43
+ "Ban corporate marketing fluff (empower, seamless, streamline, leverage). "
44
+ "Do not include customer-service greetings or conversational sign-offs ('Certainly!', 'Happy to help!'). "
45
+ "Use straight ASCII characters only (no em dashes, no curly quotes). State facts directly using 'is' or 'are'."
46
+ ),
47
+ }
48
+
49
+
50
+ def get_adapter_prompt(model: str, profile: DialectProfile | None = None) -> str:
51
+ """Generate system prompt priming block for a specified model and dialect."""
52
+ target_profile = profile or resolve_locale()
53
+ key = model.strip().lower()
54
+
55
+ base_instruction = ADAPTER_TEMPLATES.get(key)
56
+ if not base_instruction:
57
+ base_instruction = (
58
+ "[WRITING INVARIANT] Use straight ASCII characters only. Contractions are prohibited. "
59
+ "Deliver factual analysis directly without conversational filler or synthetic balance."
60
+ )
61
+
62
+ dialect_rule = (
63
+ f"You must write in {target_profile.name}."
64
+ )
65
+ if not target_profile.allow_ize_spelling:
66
+ dialect_rule += " Use -ise spellings (organise, analyse, prioritise) and British/Commonwealth orthography (colour, centre)."
67
+ if not target_profile.allow_serial_comma:
68
+ dialect_rule += " Do not use the Oxford comma before conjunctions in lists."
69
+
70
+ return f"{dialect_rule} {base_instruction}"
71
+
72
+
73
+ __all__ = ["get_adapter_prompt", "ADAPTER_TEMPLATES"]
@@ -0,0 +1,21 @@
1
+ <!-- writing-quality: off -->
2
+ <!-- agent-prose: off -->
3
+ # Anthropic Claude System Prompt Adapter
4
+
5
+ > Created and maintained by Nitivra.
6
+ > Dialect: Enforces chosen regional profile (default: en-AU).
7
+
8
+ ## Prompt Priming Block
9
+
10
+ Copy the following block into your Claude system prompt, `CLAUDE.md`, or agent configuration:
11
+
12
+ ```text
13
+ [WRITING INVARIANT]
14
+ You must adhere strictly to clear, direct technical prose:
15
+ 1. Ban prestige filler stems: crucial, nuance, bedrock, linchpin, cornerstone, bespoke, imperative, paramount.
16
+ 2. Avoid synthetic balance constructions such as "not only X, but also Y".
17
+ 3. Avoid concessive hedging phrases like "that being said" or "to be sure".
18
+ 4. State technical trade-offs directly with precise numbers, concrete metrics, and architectural dependencies.
19
+ 5. Use straight ASCII punctuation only: replace Unicode em dashes with double hyphens (--) and use straight quotes.
20
+ 6. Do not use contractions. Write full words: "do not", "cannot", "it is".
21
+ ```
@@ -0,0 +1,21 @@
1
+ <!-- writing-quality: off -->
2
+ <!-- agent-prose: off -->
3
+ # Microsoft Copilot System Prompt Adapter
4
+
5
+ > Created and maintained by Nitivra.
6
+ > Dialect: Enforces chosen regional profile (default: en-AU).
7
+
8
+ ## Prompt Priming Block
9
+
10
+ Copy the following block into your Copilot instructions, workspace instructions (`.github/copilot-instructions.md`) or agent configuration:
11
+
12
+ ```text
13
+ [WRITING INVARIANT]
14
+ You must adhere strictly to clear, direct technical prose:
15
+ 1. Enforce target dialect orthography (when using en-AU: -ise spellings, colour, centre).
16
+ 2. Ban corporate marketing fluff (empower, seamless, streamline, leverage, comprehensive).
17
+ 3. Do not include customer-service greetings or conversational sign-offs ("Certainly!", "Happy to help!", "I hope this assists you!").
18
+ 4. State facts directly using direct copulas ("is", "are"). Prohibit evasive copulas ("serves as", "stands as", "marks a").
19
+ 5. Use straight ASCII characters only: replace Unicode em dashes with double hyphens (--) and use straight quotes.
20
+ 6. Do not use contractions. Write full words: "do not", "cannot", "it is".
21
+ ```
@@ -0,0 +1,21 @@
1
+ <!-- writing-quality: off -->
2
+ <!-- agent-prose: off -->
3
+ # Google Gemini System Prompt Adapter
4
+
5
+ > Created and maintained by Nitivra.
6
+ > Dialect: Enforces chosen regional profile (default: en-AU).
7
+
8
+ ## Prompt Priming Block
9
+
10
+ Copy the following block into your Gemini system instructions, `GEMINI.md`, or agent configuration:
11
+
12
+ ```text
13
+ [WRITING INVARIANT]
14
+ You must adhere strictly to clear, direct technical prose:
15
+ 1. Vary sentence lengths deliberately: alternate short punchy statements (6 words) with compound sentences (20 to 28 words).
16
+ 2. Avoid monotone bullet lists and formulaic paragraph openers.
17
+ 3. Prohibit trailing participial clauses (", ensuring...", ", enabling...", ", providing..."). State outcomes as direct full sentences.
18
+ 4. Limit sentences to at most two commas.
19
+ 5. Use straight ASCII punctuation only: replace Unicode em dashes with double hyphens (--) and use straight quotes.
20
+ 6. Do not use contractions. Write full words: "do not", "cannot", "it is".
21
+ ```
@@ -0,0 +1,20 @@
1
+ <!-- writing-quality: off -->
2
+ <!-- agent-prose: off -->
3
+ # xAI Grok System Prompt Adapter
4
+
5
+ > Created and maintained by Nitivra.
6
+ > Dialect: Enforces chosen regional profile (default: en-AU).
7
+
8
+ ## Prompt Priming Block
9
+
10
+ Copy the following block into your Grok system prompt, `.grok/rules`, or agent configuration:
11
+
12
+ ```text
13
+ [WRITING INVARIANT]
14
+ You must adhere strictly to clear, direct technical prose:
15
+ 1. Eliminate informal conversational swagger ("Here is the kicker", "Let us dive in", "Buckle up").
16
+ 2. Prohibit raw flow arrows (->) in narrative prose. Express sequences using plain prose verbs: "then", "feeds into", "maps to".
17
+ 3. Maintain rigorous engineering delivery and avoid throat-clearing rhetorical questions.
18
+ 4. Use straight ASCII punctuation only: replace Unicode em dashes with double hyphens (--) and use straight quotes.
19
+ 5. Do not use contractions. Write full words: "do not", "cannot", "it is".
20
+ ```
@@ -0,0 +1,20 @@
1
+ <!-- writing-quality: off -->
2
+ <!-- agent-prose: off -->
3
+ # Meta Muse / Llama System Prompt Adapter
4
+
5
+ > Created and maintained by Nitivra.
6
+ > Dialect: Enforces chosen regional profile (default: en-AU).
7
+
8
+ ## Prompt Priming Block
9
+
10
+ Copy the following block into your Muse or Llama system prompt or agent configuration:
11
+
12
+ ```text
13
+ [WRITING INVARIANT]
14
+ You must adhere strictly to clear, direct technical prose:
15
+ 1. Enforce target dialect orthography (when using en-AU: -ise spellings, colour, centre).
16
+ 2. Avoid informal conversational scaffolding ("Let me unpack this", "Here is why").
17
+ 3. Use straight ASCII characters only: replace Unicode em dashes with double hyphens (--) and use straight quotes.
18
+ 4. Do not use contractions. Write full words: "do not", "cannot", "it is".
19
+ 5. Deliver factual analysis directly without sycophantic praise or conversational pleasantries.
20
+ ```
agent_prose/cli.py ADDED
@@ -0,0 +1,220 @@
1
+ """
2
+ Command-line interface for agent-prose.
3
+ Created and maintained by Nitivra.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import json
9
+ import os
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import List, Sequence
13
+
14
+ from agent_prose import __version__
15
+ from agent_prose.engine import Finding, ProseEngine, ScanResult
16
+ from agent_prose.profiles import resolve_locale
17
+
18
+ SCANNABLE_EXTENSIONS = {".md", ".txt", ".html"}
19
+ DEFAULT_EXCLUDES = {
20
+ ".git", "node_modules", ".venv", "venv", "__pycache__",
21
+ "site-packages", "dist", "build", ".pytest_cache", "fixtures",
22
+ }
23
+
24
+
25
+ def should_scan_file(filepath: Path) -> bool:
26
+ """Determine whether a file should be scanned based on extension and path."""
27
+ if filepath.suffix.lower() not in SCANNABLE_EXTENSIONS:
28
+ return False
29
+ for part in filepath.parts:
30
+ if part in DEFAULT_EXCLUDES:
31
+ return False
32
+ return True
33
+
34
+
35
+ def collect_files(targets: Sequence[str]) -> List[Path]:
36
+ """Resolve file and directory targets into a unique list of paths."""
37
+ collected: List[Path] = []
38
+ for target in targets:
39
+ p = Path(target).resolve()
40
+ if p.is_file():
41
+ collected.append(p)
42
+ elif p.is_dir():
43
+ for root, dirs, files in os.walk(p):
44
+ dirs[:] = [d for d in dirs if d not in DEFAULT_EXCLUDES]
45
+ for file in files:
46
+ file_path = Path(root) / file
47
+ if should_scan_file(file_path):
48
+ collected.append(file_path)
49
+ return collected
50
+
51
+
52
+ def format_text_output(result: ScanResult, filepath_display: str) -> str:
53
+ """Format scan results for standard terminal display."""
54
+ if result.is_exempt:
55
+ return f"{filepath_display}: [EXEMPT] Opted out of stylometric scan"
56
+ if not result.findings:
57
+ return f"{filepath_display}: [PASSED] Clean"
58
+
59
+ lines = [f"{filepath_display} (Locale: {result.profile_code}):"]
60
+ for finding in result.findings:
61
+ loc = f"L{finding.line_number}: " if finding.line_number else ""
62
+ lines.append(f" [{finding.severity}] {loc}{finding.message}")
63
+ return "\n".join(lines)
64
+
65
+
66
+ def run_scan(
67
+ paths: Sequence[str],
68
+ locale_override: str | None = None,
69
+ strict: bool = False,
70
+ advisory_threshold: int = 4,
71
+ json_mode: bool = False,
72
+ quiet: bool = False,
73
+ ) -> int:
74
+ """Execute scan across targeted paths and return the process exit code."""
75
+ profile = resolve_locale(locale_override)
76
+ engine = ProseEngine(profile=profile, strict=strict, advisory_threshold=advisory_threshold)
77
+
78
+ # Stdin handling if requested via '-'
79
+ if len(paths) == 1 and paths[0] == "-":
80
+ content = sys.stdin.read()
81
+ lines = content.splitlines()
82
+ res = engine.check(lines, filepath="stdin.md")
83
+ if json_mode:
84
+ payload = {
85
+ "file": "stdin",
86
+ "locale": res.profile_code,
87
+ "passed": res.passed,
88
+ "exit_code": res.exit_code,
89
+ "findings": [
90
+ {
91
+ "check_id": f.check_id,
92
+ "severity": f.severity,
93
+ "line": f.line_number,
94
+ "message": f.message,
95
+ }
96
+ for f in res.findings
97
+ ],
98
+ }
99
+ print(json.dumps(payload, indent=2))
100
+ else:
101
+ print(format_text_output(res, "stdin"))
102
+ return res.exit_code
103
+
104
+ targets = paths if paths else ["."]
105
+ files = collect_files(targets)
106
+
107
+ if not files:
108
+ if not quiet:
109
+ print("No matching files found to scan.")
110
+ return 0
111
+
112
+ worst_exit_code = 0
113
+ total_findings = 0
114
+ results_payload = []
115
+
116
+ for file_path in files:
117
+ try:
118
+ with open(file_path, "r", encoding="utf-8") as f:
119
+ lines = f.readlines()
120
+ except (IOError, UnicodeDecodeError) as err:
121
+ if not quiet:
122
+ print(f"Error reading {file_path}: {err}", file=sys.stderr)
123
+ worst_exit_code = max(worst_exit_code, 1)
124
+ continue
125
+
126
+ res = engine.check(lines, filepath=str(file_path))
127
+ total_findings += len(res.findings)
128
+ worst_exit_code = max(worst_exit_code, res.exit_code)
129
+
130
+ if json_mode:
131
+ results_payload.append({
132
+ "file": str(file_path),
133
+ "locale": res.profile_code,
134
+ "passed": res.passed,
135
+ "exit_code": res.exit_code,
136
+ "findings": [
137
+ {
138
+ "check_id": f.check_id,
139
+ "severity": f.severity,
140
+ "line": f.line_number,
141
+ "message": f.message,
142
+ }
143
+ for f in res.findings
144
+ ],
145
+ })
146
+ else:
147
+ if not res.passed or not quiet:
148
+ print(format_text_output(res, str(file_path)))
149
+
150
+ if json_mode:
151
+ summary = {
152
+ "version": __version__,
153
+ "files_scanned": len(files),
154
+ "worst_exit_code": worst_exit_code,
155
+ "results": results_payload,
156
+ }
157
+ print(json.dumps(summary, indent=2))
158
+ elif not quiet:
159
+ status = "PASSED" if worst_exit_code == 0 else f"FAILED (code {worst_exit_code})"
160
+ print(f"\nScan complete: {len(files)} file(s) evaluated. Status: {status}.")
161
+
162
+ return worst_exit_code
163
+
164
+
165
+ def main(argv: Sequence[str] | None = None) -> None:
166
+ """CLI entry point for agent-prose."""
167
+ parser = argparse.ArgumentParser(
168
+ prog="agent-prose",
169
+ description="Deterministic Anti-RLHF Stylometric Gate for Autonomous Agents by Nitivra.",
170
+ )
171
+ parser.add_argument(
172
+ "paths",
173
+ nargs="*",
174
+ help="Paths to markdown, text, or HTML files and directories (use '-' for stdin).",
175
+ )
176
+ parser.add_argument(
177
+ "-l", "--locale",
178
+ help="Dialect profile override (e.g. en-AU, en-US, en-GB).",
179
+ )
180
+ parser.add_argument(
181
+ "--strict",
182
+ action="store_true",
183
+ help="Enforce strict mode: promote all advisory warnings to blocking failures (exit code 1).",
184
+ )
185
+ parser.add_argument(
186
+ "--advisory-threshold",
187
+ type=int,
188
+ default=4,
189
+ help="Number of advisory warnings required to trigger exit code 2 (default: 4).",
190
+ )
191
+ parser.add_argument(
192
+ "--json",
193
+ action="store_true",
194
+ help="Output structured JSON results.",
195
+ )
196
+ parser.add_argument(
197
+ "-q", "--quiet",
198
+ action="store_true",
199
+ help="Suppress output for passing files.",
200
+ )
201
+ parser.add_argument(
202
+ "-v", "--version",
203
+ action="version",
204
+ version=f"agent-prose {__version__}",
205
+ )
206
+
207
+ args = parser.parse_args(argv)
208
+ code = run_scan(
209
+ paths=args.paths,
210
+ locale_override=args.locale,
211
+ strict=args.strict,
212
+ advisory_threshold=args.advisory_threshold,
213
+ json_mode=args.json,
214
+ quiet=args.quiet,
215
+ )
216
+ sys.exit(code)
217
+
218
+
219
+ if __name__ == "__main__":
220
+ main()