11import { existsSync , readFileSync } from 'node:fs' ;
22import { join } from 'node:path' ;
3- import { MANAGED_SECTION_BEGIN , TARGETS } from './agent-targets.js' ;
3+ import { MANAGED_SECTION_BEGIN , MANAGED_SECTION_END , TARGETS } from './agent-targets.js' ;
44import { defaultCredentialsPath , readProfile } from './credentials.js' ;
55import type { OutputMode } from './output.js' ;
66
@@ -44,8 +44,8 @@ export interface SkillPresenceDeps {
4444 * True if the `testsprite-verify` skill is installed for ANY supported agent in
4545 * `dir`. own-file targets (claude/cursor/cline/antigravity): the landing file
4646 * exists. managed-section target (codex / AGENTS.md): the file exists AND
47- * carries our BEGIN sentinel — a user-authored AGENTS.md without the sentinel
48- * does NOT count as our skill.
47+ * carries one complete managed section — a user-authored AGENTS.md without the
48+ * sentinels, or with a truncated section, does NOT count as our skill.
4949 *
5050 * The TARGETS table is the single source of truth for landing paths, so this
5151 * stays in lockstep with `agent install` without re-listing paths. Best-effort:
@@ -59,7 +59,7 @@ export function isVerifySkillInstalled(dir: string, deps: SkillPresenceDeps = {}
5959 if ( ! exists ( full ) ) continue ;
6060 if ( spec . mode === 'managed-section' ) {
6161 try {
62- if ( read ( full ) . includes ( MANAGED_SECTION_BEGIN ) ) return true ;
62+ if ( hasCompleteManagedSection ( read ( full ) ) ) return true ;
6363 } catch {
6464 // unreadable AGENTS.md → treat this target as absent, keep checking
6565 }
@@ -70,6 +70,14 @@ export function isVerifySkillInstalled(dir: string, deps: SkillPresenceDeps = {}
7070 return false ;
7171}
7272
73+ /** True when a managed-section file contains an ordered TestSprite BEGIN/END pair. */
74+ function hasCompleteManagedSection ( content : string ) : boolean {
75+ const begin = content . indexOf ( MANAGED_SECTION_BEGIN ) ;
76+ if ( begin === - 1 ) return false ;
77+ const end = content . indexOf ( MANAGED_SECTION_END , begin + MANAGED_SECTION_BEGIN . length ) ;
78+ return end !== - 1 ;
79+ }
80+
7381export interface SkillNudgeContext {
7482 /** Full command path, e.g. "test run" / "auth whoami". */
7583 commandPath : string ;
@@ -133,6 +141,7 @@ export function maybeEmitSkillNudge(ctx: SkillNudgeContext): void {
133141 }
134142}
135143
144+ /** Interpret common env-var spellings for an enabled opt-out flag. */
136145function isTruthyEnv ( v : string | undefined ) : boolean {
137146 if ( v === undefined ) return false ;
138147 const s = v . trim ( ) . toLowerCase ( ) ;
0 commit comments