diff --git a/packages/phpcs-standards/SiteKit/Sniffs/Commenting/DocCommentFullStopSniff.php b/packages/phpcs-standards/SiteKit/Sniffs/Commenting/DocCommentFullStopSniff.php
new file mode 100644
index 00000000000..f908271afc1
--- /dev/null
+++ b/packages/phpcs-standards/SiteKit/Sniffs/Commenting/DocCommentFullStopSniff.php
@@ -0,0 +1,182 @@
+getTokens();
+ $token = $tokens[ $stack_ptr ]['code'];
+
+ // Handle inline comments separately.
+ if ( T_COMMENT === $token ) {
+ $this->check_inline_comment( $phpcs_file, $stack_ptr );
+ return;
+ }
+
+ // For PHPDoc: check if immediately before this element is a docblock.
+ $find = array(
+ T_WHITESPACE,
+ T_ABSTRACT,
+ T_FINAL,
+ T_PUBLIC,
+ T_PROTECTED,
+ T_PRIVATE,
+ T_STATIC,
+ T_VAR,
+ );
+
+ $comment_end = $phpcs_file->findPrevious( $find, ( $stack_ptr - 1 ), null, true );
+ if ( false === $comment_end || T_DOC_COMMENT_CLOSE_TAG !== $tokens[ $comment_end ]['code'] ) {
+ return;
+ }
+
+ $comment_start = $tokens[ $comment_end ]['comment_opener'];
+
+ // Collect the short summary (all lines before a blank or @tag).
+ $summary_line_ptrs = array();
+ for ( $i = ( $comment_start + 1 ); $i < $comment_end; $i++ ) {
+ $code = $tokens[ $i ]['code'];
+
+ if ( T_DOC_COMMENT_STRING === $code ) {
+ $content = trim( $tokens[ $i ]['content'] );
+ if ( '' === $content ) {
+ break;
+ }
+ if ( 0 === strpos( $content, '@' ) ) {
+ break;
+ }
+ $summary_line_ptrs[] = $i;
+ continue;
+ }
+
+ if ( T_DOC_COMMENT_TAG === $code ) {
+ break;
+ }
+ }
+
+ if ( array() === $summary_line_ptrs ) {
+ return;
+ }
+
+ $last_ptr = end( $summary_line_ptrs );
+ $last_content = trim( $tokens[ $last_ptr ]['content'] );
+
+ if ( false === $this->needs_full_stop( $last_content ) ) {
+ return;
+ }
+
+ $error = 'PHPDoc summary must end with a full stop.';
+ $fix = $phpcs_file->addFixableError( $error, $last_ptr, 'MissingFullStop' );
+
+ if ( true === $fix ) {
+ $phpcs_file->fixer->replaceToken( $last_ptr, rtrim( $tokens[ $last_ptr ]['content'] ) . '.' );
+ }
+ }
+
+ /**
+ * Check inline comments for full stop.
+ *
+ * @param File $phpcs_file The file being scanned.
+ * @param int $stack_ptr Current token position.
+ * @return void
+ */
+ private function check_inline_comment( File $phpcs_file, int $stack_ptr ): void {
+ $tokens = $phpcs_file->getTokens();
+
+ // Only process if this is the first line of a contiguous // comment block.
+ $prev = $stack_ptr - 1;
+ if ( $prev >= 0 && T_COMMENT === $tokens[ $prev ]['code'] && 0 === strpos( trim( $tokens[ $prev ]['content'] ), '//' ) ) {
+ return;
+ }
+
+ // Collect this block (current + any following // lines).
+ $block_ptrs = array( $stack_ptr );
+ $next = $stack_ptr + 1;
+ $total = \count( $tokens );
+
+ while ( $next < $total && T_COMMENT === $tokens[ $next ]['code'] && 0 === strpos( trim( $tokens[ $next ]['content'] ), '//' ) ) {
+ $block_ptrs[] = $next;
+ ++$next;
+ }
+
+ // Take the last non-empty line in this block.
+ $last_ptr = null;
+ foreach ( array_reverse( $block_ptrs ) as $ptr ) {
+ $content = trim( preg_replace( '#^/{2,}\s*#', '', $tokens[ $ptr ]['content'] ) );
+ if ( '' !== $content ) {
+ $last_ptr = $ptr;
+ break;
+ }
+ }
+
+ if ( null === $last_ptr ) {
+ return;
+ }
+
+ $last_content = trim( preg_replace( '#^/{2,}\s*#', '', $tokens[ $last_ptr ]['content'] ) );
+
+ // Skip annotation-like comments (e.g. @todo, @phpcs).
+ if ( 0 === strpos( $last_content, '@' ) ) {
+ return;
+ }
+
+ if ( false === $this->needs_full_stop( $last_content ) ) {
+ return;
+ }
+
+ $error = 'Inline comment must end with a full stop.';
+ $fix = $phpcs_file->addFixableError( $error, $last_ptr, 'InlineMissingFullStop' );
+
+ if ( true === $fix ) {
+ $new = rtrim( $tokens[ $last_ptr ]['content'] ) . '.';
+ $phpcs_file->fixer->replaceToken( $last_ptr, $new );
+ }
+ }
+
+ /**
+ * Check if text needs a full stop.
+ *
+ * @param string $text The text to check.
+ * @return bool True if a full stop is needed, false otherwise.
+ */
+ private function needs_full_stop( string $text ): bool {
+ // Ignore if it already ends correctly.
+ return ( 1 !== preg_match( '/[.?!]$/u', $text ) );
+ }
+}
diff --git a/phpcs.xml b/phpcs.xml
index 40cdefaa167..d07c6e2fcba 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -61,6 +61,7 @@
+